-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbenchmark.py
More file actions
208 lines (174 loc) · 7.05 KB
/
Copy pathbenchmark.py
File metadata and controls
208 lines (174 loc) · 7.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
"""
Benchmarking & Profiling Suite for NumPy Transformer
Measures:
- Parameter counts & memory footprint
- Theoretical FLOPs per forward/backward pass (Kaplan et al. scaling laws)
- Throughput (tokens/sec) for training and inference
- Micro-benchmark latency breakdown per component (Embeddings, Attention, MLP, Norm)
"""
import time
import argparse
from typing import Dict, Any, Optional
import numpy as np
from gpt_numpy import GPT, MultiHeadAttention, FeedForward, LayerNorm
MLP = FeedForward
from layers import RMSNorm, SwiGLU
from train import cross_entropy_loss
from optimizers import AdamW
def count_parameters(model: GPT) -> int:
"""Calculate total trainable parameter count."""
params = model.get_parameters()
return sum(p.size for _, p in params)
def estimate_flops_per_token(
vocab_size: int,
n_layer: int,
d_model: int,
seq_len: int,
num_heads: int,
mlp_hidden_dim: Optional[int] = None
) -> Dict[str, float]:
"""
Calculate theoretical FLOPs per forward token using standard transformer formula:
Attention: 2 * (4 * d_model^2) + 2 * (d_model * seq_len) + 2 * (d_model * seq_len) = 8 * d_model^2 + 4 * d_model * seq_len
MLP: 2 * (2 * d_model * hidden_dim) = 4 * d_model * hidden_dim (or 6 for SwiGLU)
Logits: 2 * d_model * vocab_size
"""
hidden_dim = mlp_hidden_dim or (4 * d_model)
attn_flops = 8 * (d_model ** 2) + 4 * d_model * seq_len
mlp_flops = 4 * d_model * hidden_dim
layer_flops = attn_flops + mlp_flops
total_model_flops = n_layer * layer_flops + (2 * d_model * vocab_size)
# Backward pass is approximately 2x forward FLOPs
training_flops_per_token = 3 * total_model_flops
return {
"forward_flops_per_token": total_model_flops,
"backward_flops_per_token": 2 * total_model_flops,
"training_flops_per_token": training_flops_per_token,
"layer_flops": layer_flops
}
def benchmark_throughput(
vocab_size: int = 256,
d_model: int = 128,
num_heads: int = 4,
num_layers: int = 4,
seq_len: int = 64,
batch_size: int = 8,
warmup_iters: int = 3,
benchmark_iters: int = 10
) -> Dict[str, Any]:
"""Profile inference and training forward/backward throughput."""
print("=" * 60)
print(f"Profiling GPT [Layers: {num_layers}, Dim: {d_model}, Heads: {num_heads}, Seq: {seq_len}, Batch: {batch_size}]")
print("=" * 60)
model = GPT(
vocab_size=vocab_size,
d_model=d_model,
num_heads=num_heads,
num_layers=num_layers,
d_ff=4 * d_model,
max_seq_len=seq_len,
dropout=0.0
)
num_params = count_parameters(model)
print(f"Total Parameters: {num_params:,} ({num_params * 4 / (1024**2):.2f} MB float32)")
x = np.random.randint(0, vocab_size, size=(batch_size, seq_len))
targets = np.random.randint(0, vocab_size, size=(batch_size, seq_len))
# Warmup
for _ in range(warmup_iters):
logits = model.forward(x)
# Benchmark Inference
t0 = time.perf_counter()
for _ in range(benchmark_iters):
logits = model.forward(x)
t1 = time.perf_counter()
infer_time = (t1 - t0) / benchmark_iters
total_tokens = batch_size * seq_len
infer_throughput = total_tokens / infer_time
# Benchmark Training step (Forward + Backward)
for _ in range(warmup_iters):
logits = model.forward(x)
loss, grad_logits = cross_entropy_loss(logits, targets)
model.backward(grad_logits)
t0 = time.perf_counter()
for _ in range(benchmark_iters):
logits = model.forward(x)
loss, grad_logits = cross_entropy_loss(logits, targets)
model.backward(grad_logits)
t1 = time.perf_counter()
train_time = (t1 - t0) / benchmark_iters
train_throughput = total_tokens / train_time
flops_info = estimate_flops_per_token(vocab_size, num_layers, d_model, seq_len, num_heads)
gflops_achieved = (flops_info["training_flops_per_token"] * train_throughput) / 1e9
print(f"\n--- Benchmark Results ---")
print(f"Inference Latency: {infer_time * 1000:.2f} ms / step")
print(f"Inference Throughput: {infer_throughput:.1f} tokens/sec")
print(f"Training Latency: {train_time * 1000:.2f} ms / step (Forward + Backward)")
print(f"Training Throughput: {train_throughput:.1f} tokens/sec")
print(f"Achieved Compute: {gflops_achieved:.3f} GFLOPs/s")
print("=" * 60)
return {
"num_params": num_params,
"infer_latency_ms": infer_time * 1000,
"infer_throughput_tok_s": infer_throughput,
"train_latency_ms": train_time * 1000,
"train_throughput_tok_s": train_throughput,
"gflops": gflops_achieved
}
def benchmark_layer_breakdown(d_model: int = 128, num_heads: int = 4, seq_len: int = 64, batch_size: int = 8):
"""Profile latency of individual architectural subcomponents."""
print("\n--- Microbenchmark: Layer Breakdown ---")
x = np.random.randn(batch_size, seq_len, d_model).astype(np.float32)
# 1. MultiHeadAttention
attn = MultiHeadAttention(d_model=d_model, num_heads=num_heads, dropout=0.0)
t0 = time.perf_counter()
for _ in range(50):
_ = attn.forward(x)
attn_time = (time.perf_counter() - t0) / 50 * 1000
# 2. MLP
mlp = MLP(d_model=d_model, d_ff=4 * d_model, dropout=0.0)
t0 = time.perf_counter()
for _ in range(50):
_ = mlp.forward(x)
mlp_time = (time.perf_counter() - t0) / 50 * 1000
# 3. SwiGLU
swiglu = SwiGLU(d_model=d_model)
t0 = time.perf_counter()
for _ in range(50):
_ = swiglu.forward(x)
swiglu_time = (time.perf_counter() - t0) / 50 * 1000
# 4. LayerNorm
ln = LayerNorm(normalized_shape=d_model)
t0 = time.perf_counter()
for _ in range(50):
_ = ln.forward(x)
ln_time = (time.perf_counter() - t0) / 50 * 1000
# 5. RMSNorm
rmsnorm = RMSNorm(d_model=d_model)
t0 = time.perf_counter()
for _ in range(50):
_ = rmsnorm.forward(x)
rmsnorm_time = (time.perf_counter() - t0) / 50 * 1000
print(f"Multi-Head Attention: {attn_time:.3f} ms")
print(f"Standard GELU MLP: {mlp_time:.3f} ms")
print(f"Modern SwiGLU FFN: {swiglu_time:.3f} ms")
print(f"Standard LayerNorm: {ln_time:.3f} ms")
print(f"RMSNorm: {rmsnorm_time:.3f} ms ({((ln_time - rmsnorm_time) / ln_time) * 100:+.1f}% vs LayerNorm)")
print("-" * 60)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="NumPy Transformer Benchmark")
parser.add_argument("--d_model", type=int, default=128)
parser.add_argument("--num_layers", type=int, default=4)
parser.add_argument("--seq_len", type=int, default=64)
parser.add_argument("--batch_size", type=int, default=4)
args = parser.parse_args()
benchmark_throughput(
d_model=args.d_model,
num_layers=args.num_layers,
seq_len=args.seq_len,
batch_size=args.batch_size
)
benchmark_layer_breakdown(
d_model=args.d_model,
seq_len=args.seq_len,
batch_size=args.batch_size
)