Skip to content

Commit e717df8

Browse files
committed
test-perf
stack-info: PR: #55, branch: drisspg/stack/14
1 parent e5699dd commit e717df8

1 file changed

Lines changed: 233 additions & 0 deletions

File tree

transformer_nuggets/cute/fav4.py

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Benchmark comparing PyTorch native attention vs CUTE implementation.
4+
This benchmark has no dependencies on other flash-attn components.
5+
"""
6+
7+
import math
8+
import time
9+
import torch
10+
import torch.nn.functional as F
11+
from typing import Tuple, Optional
12+
13+
try:
14+
from flash_attn.cute import flash_attn_func
15+
CUTE_AVAILABLE = True
16+
except ImportError:
17+
CUTE_AVAILABLE = False
18+
print("Warning: CUTE implementation not available. Install with: pip install nvidia-cutlass-dsl>=4.1.0.dev0")
19+
20+
21+
def attention_pytorch(
22+
q: torch.Tensor,
23+
k: torch.Tensor,
24+
v: torch.Tensor,
25+
causal: bool = False,
26+
softmax_scale: Optional[float] = None
27+
) -> torch.Tensor:
28+
"""
29+
Standard PyTorch attention implementation.
30+
31+
Args:
32+
q: Query tensor (batch, seqlen, nheads, headdim)
33+
k: Key tensor (batch, seqlen, nheads_kv, headdim)
34+
v: Value tensor (batch, seqlen, nheads_kv, headdim)
35+
causal: Whether to apply causal masking
36+
softmax_scale: Scale factor for attention scores
37+
38+
Returns:
39+
output: (batch, seqlen, nheads, headdim)
40+
"""
41+
batch_size, seqlen_q, nheads, headdim = q.shape
42+
_, seqlen_k, nheads_kv, _ = k.shape
43+
44+
if softmax_scale is None:
45+
softmax_scale = 1.0 / math.sqrt(headdim)
46+
47+
# Handle grouped query attention
48+
if nheads != nheads_kv:
49+
# Repeat k and v to match number of query heads
50+
repeat_factor = nheads // nheads_kv
51+
k = k.repeat_interleave(repeat_factor, dim=2)
52+
v = v.repeat_interleave(repeat_factor, dim=2)
53+
54+
# Compute attention scores
55+
scores = torch.einsum('bqhd,bkhd->bhqk', q, k) * softmax_scale
56+
57+
if causal:
58+
causal_mask = torch.triu(
59+
torch.full((seqlen_q, seqlen_k), float('-inf'), device=q.device, dtype=q.dtype),
60+
diagonal=1
61+
)
62+
scores = scores + causal_mask
63+
64+
# Apply softmax
65+
attn_weights = torch.softmax(scores, dim=-1)
66+
67+
# Apply attention to values
68+
output = torch.einsum('bhqk,bkhd->bqhd', attn_weights, v)
69+
70+
return output
71+
72+
73+
def benchmark_function(func, *args, **kwargs):
74+
"""Benchmark a function with warmup and multiple runs."""
75+
device = args[0].device
76+
77+
# Warmup
78+
for _ in range(5):
79+
with torch.no_grad():
80+
_ = func(*args, **kwargs)
81+
torch.cuda.synchronize(device)
82+
83+
# Benchmark
84+
torch.cuda.synchronize(device)
85+
start_time = time.time()
86+
87+
for _ in range(10):
88+
with torch.no_grad():
89+
result = func(*args, **kwargs)
90+
torch.cuda.synchronize(device)
91+
92+
end_time = time.time()
93+
avg_time = (end_time - start_time) / 10
94+
95+
return avg_time, result
96+
97+
98+
def compute_flops(batch_size: int, seqlen: int, nheads: int, headdim: int, causal: bool = False) -> int:
99+
"""Compute theoretical FLOPs for attention."""
100+
# QK^T: batch_size * nheads * seqlen * seqlen * headdim
101+
# Softmax: batch_size * nheads * seqlen * seqlen
102+
# AV: batch_size * nheads * seqlen * seqlen * headdim
103+
qk_flops = batch_size * nheads * seqlen * seqlen * headdim
104+
softmax_flops = batch_size * nheads * seqlen * seqlen * 5 # approx for softmax
105+
av_flops = batch_size * nheads * seqlen * seqlen * headdim
106+
107+
total_flops = qk_flops + softmax_flops + av_flops
108+
109+
# Causal attention reduces computation by ~half
110+
if causal:
111+
total_flops = total_flops // 2
112+
113+
return total_flops
114+
115+
116+
def run_benchmark():
117+
"""Run the benchmark comparing PyTorch vs CUTE."""
118+
119+
if not CUTE_AVAILABLE:
120+
print("CUTE implementation not available. Exiting.")
121+
return
122+
123+
device = 'cuda' if torch.cuda.is_available() else 'cpu'
124+
if device == 'cpu':
125+
print("CUDA not available. This benchmark requires GPU.")
126+
return
127+
128+
dtype = torch.float16
129+
130+
# Test configurations
131+
configs = [
132+
# (batch_size, seqlen, nheads, headdim, causal)
133+
(2, 512, 16, 64, False),
134+
(2, 512, 16, 64, True),
135+
(1, 1024, 16, 64, False),
136+
(1, 1024, 16, 64, True),
137+
(1, 2048, 16, 64, False),
138+
(1, 2048, 16, 64, True),
139+
(2, 512, 16, 128, False),
140+
(2, 512, 16, 128, True),
141+
(1, 1024, 16, 128, False),
142+
(1, 1024, 16, 128, True),
143+
]
144+
145+
print("=" * 80)
146+
print("PyTorch vs CUTE Flash Attention Benchmark")
147+
print("=" * 80)
148+
print(f"Device: {device}")
149+
print(f"Dtype: {dtype}")
150+
print()
151+
152+
results = []
153+
154+
for batch_size, seqlen, nheads, headdim, causal in configs:
155+
print(f"Config: batch={batch_size}, seqlen={seqlen}, nheads={nheads}, headdim={headdim}, causal={causal}")
156+
157+
# Generate random inputs
158+
q = torch.randn(batch_size, seqlen, nheads, headdim, device=device, dtype=dtype)
159+
k = torch.randn(batch_size, seqlen, nheads, headdim, device=device, dtype=dtype)
160+
v = torch.randn(batch_size, seqlen, nheads, headdim, device=device, dtype=dtype)
161+
162+
try:
163+
# Benchmark PyTorch
164+
pytorch_time, pytorch_output = benchmark_function(
165+
attention_pytorch, q, k, v, causal=causal
166+
)
167+
168+
# Benchmark CUTE
169+
cute_time, (cute_output, _) = benchmark_function(
170+
flash_attn_func, q, k, v, causal=causal
171+
)
172+
173+
# Compute metrics
174+
flops = compute_flops(batch_size, seqlen, nheads, headdim, causal)
175+
pytorch_tflops = (flops / pytorch_time) / 1e12
176+
cute_tflops = (flops / cute_time) / 1e12
177+
speedup = pytorch_time / cute_time
178+
179+
# Check correctness (with some tolerance for numerical differences)
180+
max_diff = torch.max(torch.abs(pytorch_output - cute_output)).item()
181+
mean_diff = torch.mean(torch.abs(pytorch_output - cute_output)).item()
182+
183+
print(f" PyTorch: {pytorch_time*1000:.2f}ms ({pytorch_tflops:.2f} TFLOPs/s)")
184+
print(f" CUTE: {cute_time*1000:.2f}ms ({cute_tflops:.2f} TFLOPs/s)")
185+
print(f" Speedup: {speedup:.2f}x")
186+
print(f" Max diff: {max_diff:.2e}, Mean diff: {mean_diff:.2e}")
187+
188+
results.append({
189+
'config': (batch_size, seqlen, nheads, headdim, causal),
190+
'pytorch_time': pytorch_time,
191+
'cute_time': cute_time,
192+
'pytorch_tflops': pytorch_tflops,
193+
'cute_tflops': cute_tflops,
194+
'speedup': speedup,
195+
'max_diff': max_diff,
196+
'mean_diff': mean_diff
197+
})
198+
199+
except Exception as e:
200+
print(f" Error: {e}")
201+
results.append({
202+
'config': (batch_size, seqlen, nheads, headdim, causal),
203+
'error': str(e)
204+
})
205+
206+
print()
207+
208+
# Summary
209+
print("=" * 80)
210+
print("SUMMARY")
211+
print("=" * 80)
212+
213+
valid_results = [r for r in results if 'error' not in r]
214+
if valid_results:
215+
avg_speedup = sum(r['speedup'] for r in valid_results) / len(valid_results)
216+
avg_cute_tflops = sum(r['cute_tflops'] for r in valid_results) / len(valid_results)
217+
avg_pytorch_tflops = sum(r['pytorch_tflops'] for r in valid_results) / len(valid_results)
218+
219+
print(f"Average speedup: {avg_speedup:.2f}x")
220+
print(f"Average CUTE performance: {avg_cute_tflops:.2f} TFLOPs/s")
221+
print(f"Average PyTorch performance: {avg_pytorch_tflops:.2f} TFLOPs/s")
222+
223+
max_errors = [r['max_diff'] for r in valid_results]
224+
print(f"Max numerical difference: {max(max_errors):.2e}")
225+
print(f"Mean numerical difference: {sum(r['mean_diff'] for r in valid_results) / len(valid_results):.2e}")
226+
227+
error_count = len([r for r in results if 'error' in r])
228+
if error_count > 0:
229+
print(f"Failed configurations: {error_count}/{len(results)}")
230+
231+
232+
if __name__ == "__main__":
233+
run_benchmark()

0 commit comments

Comments
 (0)