Reader companion · First Edition 2026 · Amazon KDP
AI Performance Engineering: From GPU Kernels to LLM Inference
by Srinivasa Rao Bittla · Author & Performance Engineering Leader
"Every optimization technique in this book — from AMP to FlashAttention to speculative decoding — ultimately serves this goal:
keep the GPU doing exactly what it was built to do, and stop it waiting for memory." — Chapter 0
Choose an example question above, or type your own.
About this book
This book is written for engineers who already know how to think about performance but need the AI-specific layer — the GPU memory model, the profiling hierarchy, and the techniques that move workloads from 40% GPU utilization to over 90%.
Tensor Cores (V100, 2017): FP16 is 8–16× faster than FP32. Dimensions must be multiples of 8 (FP16/BF16) or 16 (FP8/INT8) to avoid silent padding overhead.
Compute grew 45× since 2017; memory bandwidth grew only 9×. Most AI workloads are memory-bandwidth-bound.
Sections: Why GPUs Exist · Fixed-Function to Programmable · Generation Timeline · Tensor Cores, HBM, NVLink · Memory: The Real Constraint
Chapter 1What Is AI Performance Engineering?
Roofline Model: Pmax = min(C_peak, B_mem × I). Ridge point for A100 FP16 = 156 FLOPs/byte. LLM decode at batch=1 uses only 0.6% of A100 peak compute.
Profiling hierarchy: nsys first (GPU idle?), torch.profiler second (which operator?), ncu third (why is this kernel slow?). Always profile before optimizing.
Economics: $111/M tokens baseline → $28/M with continuous batching → $16/M with INT8 → $11/M with speculative decoding.
Sections: The AI Infrastructure Stack · The Roofline Model · Profiling Hierarchy · Economics of GPU Underutilization · Optimization Order
Chapter 2PyTorch Fundamentals
CRITICAL: time.time() measures CPU submission (~0.05 ms), NOT GPU execution. Use torch.cuda.Event(enable_timing=True) + torch.cuda.synchronize().
AMP (torch.autocast + GradScaler): runs FP16 forward/backward with FP32 master weights. 1.5–3× speedup. BF16 preferred on A100+.
Calling .item() inside the training loop forces a GPU pipeline drain — can cut throughput by 50%+. Accumulate metrics on-device, sync once per N steps.
Always warm up ≥5 iterations before timing — the first kernel launch triggers CUDA JIT compilation (100–1000 ms overhead).
Sections: Tensors · Autograd · nn.Module · The Complete Training Loop · GPU Timing — The Right Way · The 10 Most Common PyTorch Performance Mistakes
Kernel fusion reduces HBM round trips: fuse LayerNorm + linear + activation to skip intermediate HBM writes.
Sections: GPU Thread Hierarchy · Memory Coalescing and the Memory Hierarchy · Tensor Cores · Kernel Fusion
Chapter 5GPU Profiling
nsys: full system timeline (CPU + GPU + PCIe + NVLink). Long GPU-idle gaps = DataLoader or launch bottleneck. Run first.
torch.profiler: CPU+CUDA time per PyTorch operator. Find the single slowest operator before changing any code.
ncu: inside one kernel — memory throughput %, SM occupancy, Tensor Core utilization, warp stall reasons. Use third, only after identifying the slow operator.
perf stat: IPC < 1 = memory-bound or branch-heavy. LLC-load-misses > 20% = cache thrashing in the DataLoader CPU pipeline.
CPU flamegraphs: width = time. Use perf record -g + Brendan Gregg's FlameGraph. Find CPU bottlenecks before tuning GPU code.
eBPF/bpftrace: zero-overhead kernel tracing. Attach probes to syscalls, I/O, and scheduler events without recompiling.
Sections: perf stat — Hardware Event Counting · CPU Flamegraphs · eBPF and bpftrace · Lock Contention Analysis
Chapter 9Memory Hierarchy and NUMA
NUMA: GPU DMA is fastest with pinned memory on the same NUMA node as the GPU's PCIe slot. Mismatched NUMA reduces effective PCIe bandwidth.
numactl --cpunodebind=0 --membind=0 python train.py: pins process to the same NUMA node as the GPU.
Sections: The CPU Memory Hierarchy · NUMA Architecture · Memory Bandwidth as a Bottleneck
Chapter 10LLM Inference Fundamentals
Two phases: Prefill processes all input tokens in parallel (compute-bound). Decode generates one token at a time (memory-bandwidth-bound, I ≈ 1 FLOPs/byte).
KV cache: stores Key/Value tensors for processed tokens. LLaMA-2 70B FP16 at 4096-token context ≈ 8.6 GB KV cache per request.
TTFT (Time To First Token) = prefill latency, critical for interactive apps. TPS (Tokens Per Second) = decode throughput, critical for batch jobs.
Memory budget: VRAM = model weights + KV cache + activations + framework overhead. Every component must be estimated before deployment.
Sections: Scaling the KV Cache · Memory Budget Planning
Chapter 19 (Capstone)Flamegraph Challenge
Given a slow training loop, use the three-stage profiling hierarchy to isolate whether the bottleneck is in the DataLoader, the Python training loop, or the GPU kernel.
Sections: CPU-to-GPU Pipeline Diagnosis · Finding the Real Bottleneck
Chapter 20NVIDIA Blackwell — The B-Series Architecture
Decision framework: memory-bound inference → MI300X or H100 80GB. Compute-bound training → H100 or B200. Cost-sensitive → Trainium 2 or Gaudi 3.
Cost per token at scale beats cost per FLOP as the primary selection metric for production inference deployments.
Sections: Decision Framework · Cost per FLOP vs Cost per Token · When to Use Which Accelerator
Chapter 24Triton — Writing Custom GPU Kernels
Triton (OpenAI): Python-based GPU kernel language. Higher-level than CUDA, comparable performance. FlashAttention-2 is written in Triton.
Use Triton when you need a fused operation PyTorch doesn't provide natively, or custom attention patterns without writing raw CUDA C++.
Triton compiles to PTX via LLVM. The compiler handles shared memory tiling, vectorization, and warp-level operations automatically.
Sections: Why Triton Exists · The Triton Programming Model · Vector Addition · Memory Coalescing · Fused GEMM
Chapter 25FlashAttention — IO-Aware Attention
Standard attention materializes the full N×N matrix in HBM. At N=4096: 64 MB per layer per head — read AND written every forward pass.
FlashAttention tiles Q/K/V into SRAM blocks, never writes the N×N matrix to HBM. Memory complexity: O(N) instead of O(N²). 2–4× speedup for sequences >1024 tokens.
FlashAttention v2 is default in HuggingFace Transformers 4.36+ via torch.nn.functional.scaled_dot_product_attention() for FP16/BF16 inputs.
Sections: Why Attention Is Memory-Bound · Tiling and SRAM Reuse · FlashAttention v1 and v2 · Profiling the Speedup
Chapter 26Distributed Training at Scale
DDP: each worker holds a full model copy; gradients averaged via AllReduce. Best for models that fit in one GPU's VRAM.
FSDP: shards parameters across workers, gathers them for forward/backward, discards after. Enables training models larger than one GPU.
Scaling laws: compute-optimal training (Chinchilla): C_opt ≈ 6 × N × D where N = parameters, D = training tokens. Most large models are undercomputed, not overcomputed.
Sections: Data Parallelism · FSDP vs DDP · Pipeline Parallelism · Scaling Laws
Chapter 27Production Observability
DCGM: NVIDIA's production GPU telemetry. Exports SM utilization %, memory %, power, temperature, NVLink bandwidth to Prometheus.
vLLM with PagedAttention and continuous batching: 4–8× throughput improvement over naive HuggingFace generate loop.
Multi-LoRA: serve 100s of fine-tuned adapters from one base model deployment. Cost-per-token is the primary comparison metric across serving frameworks.