burt-imma / scripts /activation_benchmark.py
SNAPKITTYWEST's picture
push from SNAPKITTYWEST/burt-imma
b88c26d verified
Raw
History Blame Contribute Delete
7.93 kB
#!/usr/bin/env python3
"""
Benchmark SmoothLeaky activation against standard activations.
License: BSL-1.1
Contact: jessica@collectivekitty.com
SmoothLeaky activation:
f(x) = alpha * x + (1 - alpha) * x * sigmoid(x)
where alpha = 0.01 (default)
This combines the non-dying gradient property of LeakyReLU with the
smooth, differentiable nature of SiLU/Swish. Benchmarks forward pass,
backward pass, and peak memory usage against standard activations.
"""
import argparse
import sys
import time
from dataclasses import dataclass
from typing import Callable, List
import torch
import torch.nn as nn
import torch.nn.functional as F
class SmoothLeaky(nn.Module):
"""SmoothLeaky activation: alpha * x + (1-alpha) * x * sigmoid(x).
Combines LeakyReLU's non-dying gradients with SiLU's smoothness.
At alpha=0, reduces to SiLU/Swish.
At alpha=1, reduces to identity.
"""
def __init__(self, alpha: float = 0.01):
super().__init__()
self.alpha = alpha
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.alpha * x + (1.0 - self.alpha) * x * torch.sigmoid(x)
def extra_repr(self) -> str:
return f"alpha={self.alpha}"
class Mish(nn.Module):
"""Mish activation: x * tanh(softplus(x))."""
def forward(self, x: torch.Tensor) -> torch.Tensor:
return x * torch.tanh(F.softplus(x))
@dataclass
class BenchmarkResult:
"""Result of benchmarking a single activation function."""
name: str
forward_time_ms: float
backward_time_ms: float
peak_memory_mb: float
forward_std_ms: float
backward_std_ms: float
def benchmark_activation(
activation: nn.Module,
name: str,
batch_size: int,
dim: int,
num_iters: int,
device: torch.device,
warmup_iters: int = 50,
) -> BenchmarkResult:
"""Benchmark a single activation function."""
# Warmup
for _ in range(warmup_iters):
x = torch.randn(batch_size, dim, device=device, requires_grad=True)
y = activation(x)
loss = y.sum()
loss.backward()
if device.type == "cuda":
torch.cuda.synchronize()
torch.cuda.reset_peak_memory_stats(device)
# Benchmark forward pass
forward_times = []
for _ in range(num_iters):
x = torch.randn(batch_size, dim, device=device, requires_grad=True)
if device.type == "cuda":
torch.cuda.synchronize()
start = time.perf_counter()
y = activation(x)
if device.type == "cuda":
torch.cuda.synchronize()
end = time.perf_counter()
forward_times.append((end - start) * 1000.0) # ms
# Clean up
del y
del x
# Benchmark backward pass
backward_times = []
for _ in range(num_iters):
x = torch.randn(batch_size, dim, device=device, requires_grad=True)
y = activation(x)
loss = y.sum()
if device.type == "cuda":
torch.cuda.synchronize()
start = time.perf_counter()
loss.backward()
if device.type == "cuda":
torch.cuda.synchronize()
end = time.perf_counter()
backward_times.append((end - start) * 1000.0) # ms
del x, y, loss
# Measure peak memory
if device.type == "cuda":
torch.cuda.reset_peak_memory_stats(device)
x = torch.randn(batch_size, dim, device=device, requires_grad=True)
y = activation(x)
loss = y.sum()
loss.backward()
torch.cuda.synchronize()
peak_memory_mb = torch.cuda.max_memory_allocated(device) / (1024 * 1024)
del x, y, loss
else:
# On CPU, we estimate memory based on tensor sizes
peak_memory_mb = (batch_size * dim * 4 * 3) / (1024 * 1024) # input + output + grad (float32)
forward_times_t = torch.tensor(forward_times)
backward_times_t = torch.tensor(backward_times)
return BenchmarkResult(
name=name,
forward_time_ms=forward_times_t.mean().item(),
backward_time_ms=backward_times_t.mean().item(),
peak_memory_mb=peak_memory_mb,
forward_std_ms=forward_times_t.std().item(),
backward_std_ms=backward_times_t.std().item(),
)
def main():
parser = argparse.ArgumentParser(
description="Benchmark SmoothLeaky activation against standard activations."
)
parser.add_argument("--batch-size", type=int, default=1024, help="Batch size (default: 1024)")
parser.add_argument("--dim", type=int, default=256, help="Hidden dimension (default: 256)")
parser.add_argument("--num-iters", type=int, default=1000, help="Number of iterations (default: 1000)")
parser.add_argument("--device", type=str, default="cuda:0", help="Device (default: cuda:0)")
parser.add_argument("--alpha", type=float, default=0.01, help="SmoothLeaky alpha (default: 0.01)")
args = parser.parse_args()
# Check device availability
if "cuda" in args.device and not torch.cuda.is_available():
print(f"CUDA not available, falling back to CPU.")
args.device = "cpu"
device = torch.device(args.device)
print("BURT-IMMA Activation Benchmark")
print("=" * 80)
print(f" Batch size: {args.batch_size}")
print(f" Dimension: {args.dim}")
print(f" Iterations: {args.num_iters}")
print(f" Device: {device}")
print(f" Alpha: {args.alpha}")
if device.type == "cuda":
print(f" GPU: {torch.cuda.get_device_name(device)}")
print("=" * 80)
print()
# Define activations to benchmark
activations = [
(SmoothLeaky(alpha=args.alpha), "SmoothLeaky (ours)"),
(nn.ReLU(), "ReLU"),
(nn.LeakyReLU(negative_slope=0.01), "LeakyReLU"),
(nn.GELU(), "GELU"),
(nn.SiLU(), "SiLU/Swish"),
(Mish(), "Mish"),
]
results: List[BenchmarkResult] = []
for activation, name in activations:
activation = activation.to(device)
print(f" Benchmarking {name}...", end="", flush=True)
result = benchmark_activation(
activation=activation,
name=name,
batch_size=args.batch_size,
dim=args.dim,
num_iters=args.num_iters,
device=device,
)
results.append(result)
print(f" done ({result.forward_time_ms:.4f} ms fwd)")
print()
print("=" * 80)
print(f"{'Activation':<20} {'Fwd (ms)':<12} {'Bwd (ms)':<12} {'Mem (MB)':<12} {'Fwd Speedup':<12}")
print("-" * 80)
# Use SmoothLeaky as baseline for speedup comparison
baseline_fwd = results[0].forward_time_ms
for r in results:
speedup = baseline_fwd / r.forward_time_ms if r.forward_time_ms > 0 else float("inf")
print(
f"{r.name:<20} "
f"{r.forward_time_ms:<12.4f} "
f"{r.backward_time_ms:<12.4f} "
f"{r.peak_memory_mb:<12.2f} "
f"{speedup:<12.2f}x"
)
print("-" * 80)
print()
# Detailed statistics
print("Detailed Statistics (mean +/- std):")
print(f"{'Activation':<20} {'Forward':<24} {'Backward':<24}")
print("-" * 68)
for r in results:
fwd_str = f"{r.forward_time_ms:.4f} +/- {r.forward_std_ms:.4f} ms"
bwd_str = f"{r.backward_time_ms:.4f} +/- {r.backward_std_ms:.4f} ms"
print(f"{r.name:<20} {fwd_str:<24} {bwd_str:<24}")
print()
print("Notes:")
print(" - Speedup > 1.0 means faster than SmoothLeaky")
print(" - Speedup < 1.0 means slower than SmoothLeaky")
print(f" - SmoothLeaky(alpha={args.alpha}): f(x) = {args.alpha}*x + {1-args.alpha:.2f}*x*sigmoid(x)")
if __name__ == "__main__":
main()