22 min
Lesson 1 of 4
Profiling and Bottleneck Analysis
From the course AI Performance Optimization
Systematic profiling identifies where time and resources are actually spent in AI workloads.
GPU Profiling
PyTorch Profiler
import torch
from torch.profiler import profile, ProfilerActivity, tensorboard_trace_handler
# Profile training step
with profile(
activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA],
schedule=torch.profiler.schedule(wait=1, warmup=1, active=5),
on_trace_ready=tensorboard_trace_handler('./log/resnet18'),
record_shapes=True,
with_stack=True,
profile_memory=True
) as prof:
for step, batch in enumerate(dataloader):
if step >= 1 + 1 + 5:
break
model(batch)
prof.step()
# Key metrics
print(prof.key_averages().table(
sort_by="cuda_time_total",
row_limit=20,
header="Top 20 CUDA Ops"
))
# Output shows:
# - CPU time vs CUDA time per operation
# - Memory allocated/freed
# - Number of calls
# - Self vs total time (with children)
Identifying Bottlenecks
# Memory bandwidth test
import torch
import time
def measure_memory_bandwidth():
size_gb = 4
num_elements = size_gb * 1024**3 // 4 # float32
tensor = torch.randn(num_elements, device='cuda')
torch.cuda.synchronize()
start = time.time()
for _ in range(100):
result = tensor * 2.0 # Read + write
torch.cuda.synchronize()
elapsed = time.time() - start
# 2x size because we read AND write
bandwidth_gbps = (2 * size_gb * 100) / elapsed
print(f"Memory bandwidth: {bandwidth_gbps:.1f} GB/s")
# Compare to GPU spec (A100: 2TB/s, RTX 4090: 1TB/s)
# GPU compute utilization test
def measure_compute_utilization():
import subprocess
result = subprocess.run(
['nvidia-smi', '--query-gpu=utilization.gpu,utilization.memory',
'--format=csv,noheader'],
capture_output=True, text=True
)
print(result.stdout)
Common Bottleneck Patterns
Symptom: GPU utilization < 70%
→ Bottleneck: Data loading or CPU preprocessing
→ Fix: Increase num_workers, add prefetching, pin_memory=True
Symptom: High GPU memory utilization but low compute
→ Bottleneck: Memory bandwidth limited (memory-bound kernels)
→ Fix: Kernel fusion, larger batch sizes, Flash Attention
Symptom: Good GPU utilization but poor scaling
→ Bottleneck: NCCL communication overhead in distributed training
→ Fix: Gradient compression, topology-aware communication
Symptom: Spiky GPU utilization
→ Bottleneck: CPU-GPU synchronization or Python GIL
→ Fix: Async operations, non-blocking transfers
Key Takeaways
- •PyTorch Profiler identifies CPU vs CUDA time, memory allocation, and operation costs
- •Memory bandwidth tests reveal if workloads are compute-bound or memory-bound
- •GPU utilization below 70% typically indicates data loading or communication bottlenecks
- •Profiling before optimizing prevents wasted effort on non-bottleneck areas
0 of 4 lessons marked complete
Progress is kept in this browser only.