Master Local AI
  1. 0Load
  2. 1You type
  3. 2Tokenize
  4. 3To the GPU
  5. 4Prefill
  6. 5Decode
  7. 6Show text
Can it run?

Chapter 2 · Practitioner

Why a GPU

A GPU does thousands of multiply-adds at the same time.

  1. 0Loadonce: file → VRAM
  2. 1You typetext in the app
  3. 2TokenizeCPU: text → IDs
  4. 3To the GPUIDs cross PCIe
  5. 4Prefillwhole prompt, one pass
  6. 5Decoderead all weights → 1 token
  7. 6Show textID → word · repeat 5

1Watch

Now

Press a step on the right, or change any control under the machine.

Readout

Key to the machine

A drilling bench. Each plate is one matrix multiplication, and a bigger matrix means bigger holes. The CPU drills one hole at a time. The GPU drills 64 holes per stroke.
How the numbers work

A transformer spends most of its time in matrix multiplication: M × N outputs, each a dot product K long, all independent. A CPU has a few strong cores, like a single-spindle drill that moves from hole to hole. A GPU has thousands of lanes and tensor cores that each finish a small matrix tile per instruction, like a gang drill that bores a whole tile per stroke. Both heads drill at the same scale: holes per second follow the sustained FLOPs of each device (70% of peak), and the GPU spends about 5 µs setting up each kernel launch (the amber lamp) before it drills. Make the matrix small and the launch eats the GPU's lead. The readout keeps the real times, but the motion is not to scale: setup and drilling of a plate together are capped at about 4 s on screen, drilling is shortened to fit, and the setup lamp never stays lit more than 4× as long as the drilling (at 512 × 512 the real ratio is about 5×, at 256 × 256 about 40×). The CPU still has drilled its real share of holes when the GPU's plate is done. Drop the precision and each step down doubles the tile a tensor core finishes per instruction: the gang head grows from 64 bits (FP16) to 128 (FP8) to 256 (FP4, the whole plate in one stroke). FP32 runs on the plain CUDA cores with the tensor cores idle; the Apple M4's GPU has no FP8 or FP4 units and runs FP16 at its FP32 rate. CPU: Ryzen 9 9950X, AVX-512 FP32. GPU rates are NVIDIA's dense figures.

  • Plate = one matrix multiplication
  • Bits = 1 (CPU), 64–256 (GPU)
  • Stacks = finished plates, counted
  • Amber lamp = GPU launch setup
Speed
Open full screen ↗

2Try this

Then change anything yourself.

3Read on ↓

The workload is a matmul

A neural network is mostly matrix multiplication, and each output is independent. A linear layer multiplies activations X \in \mathbb{R}^{M\times K} by weights W \in \mathbb{R}^{K\times N}. Each of the M \cdot N outputs is a K-long dot product, for 2MNK FLOPs in total. The GPU can compute all outputs in parallel. A transformer layer holds a few such matrices (Q, K, V and output projections, and the MLP). Attention adds two batched matmuls.

  • CPU: 8–24 fast cores for branchy, serial code, with wide SIMD units. A good desktop CPU reaches a few TFLOPS at FP32.
  • GPU: dozens to ~200 streaming multiprocessors (SMs). Each SM runs threads in lockstep groups of 32 (warps). Tensor cores multiply a small tile of numbers in one instruction. A GeForce RTX 5090 has 170 SMs.

Precision is a throughput dial

Fewer bits per number give more operations per cycle on tensor cores. Each GPU generation adds a smaller format:

Format Bits Where it matters locally
FP32 / TF32 32 / 19 training master weights; rarely used for inference
BF16 / FP16 16 the "unquantized" reference for most open models
FP8 (E4M3/E5M2) 8 Ada (RTX 40) and newer tensor cores; FP8 weights and KV caches in TensorRT-LLM, vLLM and SGLang
INT8 8 classic quantization; widely supported
FP4 / NVFP4 4 Blackwell (RTX 50, RTX PRO 6000, DGX Spark) tensor cores run 4-bit floating point natively

Vendors quote "AI TOPS" at the lowest precision with 2:4 structured sparsity. This number is roughly 2× a dense number and 4× an FP8 dense number. Compare devices at the same precision and the same sparsity setting.

Lab: matrix multiply on CPU vs GPU

Time large square matmuls on the CPU and the GPU, and report TFLOPS. PyTorch runs on the CPU and on this Mac's GPU (Metal Performance Shaders, mps). On an NVIDIA machine, the same code uses cuda.

Lab · runs on your own machine

uv run labs/02_matmul.pyDownload all labs (.zip)

Needs uv and, for the live parts, Ollama with a small chat model: ollama pull qwen3:0.6b.

Show the code
# /// script
# requires-python = ">=3.11"
# dependencies = ["numpy", "torch"]
# ///
import _local as local

import time, torch

def bench(device, n=4096, dtype=torch.float32, repeats=10):
    a = torch.randn(n, n, device=device, dtype=dtype)
    b = torch.randn(n, n, device=device, dtype=dtype)
    sync = {"cuda": torch.cuda.synchronize, "mps": torch.mps.synchronize}.get(device, lambda: None)
    for _ in range(3):
        a @ b
    sync()
    start = time.perf_counter()
    for _ in range(repeats):
        a @ b
    sync()
    seconds = (time.perf_counter() - start) / repeats
    return 2 * n**3 / seconds / 1e12

devices = ["cpu"] + (["mps"] if torch.backends.mps.is_available() else []) + (["cuda"] if torch.cuda.is_available() else [])
results = {}
for device in devices:
    for dtype in (torch.float32, torch.float16):
        if device == "cpu" and dtype == torch.float16:
            continue
        results[(device, str(dtype).split(".")[-1])] = bench(device, dtype=dtype)
        print(f"{device:>4} {str(dtype).split('.')[-1]:>8}: {results[(device, str(dtype).split('.')[-1])]:6.2f} TFLOPS")
Sample output on this course's Mac (Apple M4, 32 GB)
cpu  float32:   1.10 TFLOPS
 mps  float32:   1.96 TFLOPS
 mps  float16:   2.74 TFLOPS

What to notice:

  • Even this Mac's integrated GPU beats its CPU cores on large matmuls, and FP16 beats FP32.
  • A discrete RTX GPU adds one to two orders of magnitude of tensor throughput.
  • Chapter 3 shows that, for chat, this compute mostly sits idle.

Why NVIDIA

The software matters as much as the silicon.

  • CUDA and its libraries (cuBLAS, cuDNN, CUTLASS, NCCL), plus kernels such as FlashAttention.
  • TensorRT, TensorRT-LLM and TensorRT for RTX (inference compilers), and NIM (packaged model microservices).
  • llama.cpp, Ollama, vLLM, SGLang, PyTorch and ONNX Runtime all treat NVIDIA GPUs as a first-class target. Windows ML can route models through TensorRT for RTX.

A model built on this stack runs well on the largest installed base of AI-capable GPUs.

Explain it

Answer each question aloud.

  1. Why do GPUs matter for AI? A model is billions of independent multiply-adds. A GPU does thousands of them in parallel.
  2. What do tensor cores do? They multiply a small tile of numbers per instruction at low precision. This gives many times the matmul throughput of plain CUDA cores.
  3. How do you compare two GPUs honestly? Use the same precision and sparsity. Then compare compute (prefill), bandwidth (decode) and capacity (what fits) separately.

Exercises

  1. Compute the prefill FLOPs for a 4,000-token prompt on Llama 3.1 8B, then the TTFT at an effective 100 TFLOPS.
  2. Sweep n from 256 to 8192 in the lab, and find where the GPU overtakes the CPU.