AI Model Wiki

    Skip to main content
    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.