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 12 · Master

Mastery: Measure, Optimize, Ship

Measure honestly, find the bottleneck, then ship.

  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

Test bench: your app sits in a vice, wired to four gauges. Slide in a cartridge: blue bugs show the estimate. Run measures it and lights the launch lamps.
How the numbers work

Shipping an app means measuring the right things the right way: time to first token, tokens per second at a stated context and batch, peak VRAM and power, on stated hardware, driver and runtime versions, after a warm-up, with fixed prompts. Here the prompt is 4,000 tokens (3,500 of them a shared prefix), 256 tokens out, batch 1. Each cartridge moves the gauges through _model.js: weight bits change the bytes read per token (decode speed) and the VRAM budget, an FP8 KV cache halves the cache, FlashAttention drops the score-matrix scratch, prefix caching prefills only the 500 new tokens, CUDA graphs cut per-token launch overhead (modelled as 25 µs per layer without, 3 µs with), TensorRT is assumed to lift bandwidth use from 70% to 80% and prefill efficiency from 40% to 55%, and a right-sized 8k window shrinks the cache. All numbers are estimates, not measurements: prefill uses the dense FP16 tensor rate (NVIDIA AI TOPS ÷ 8), power assumes 65% of board power while decoding. Perf needs ≥ 30 tok/s and TTFT ≤ 1 s, UX TTFT ≤ 0.5 s, Reliability ≤ 90% of memory; Perf, UX and Docs light only after a measured run. Needles move only during a run (a discarded warm-up, then the measured run); between runs they stay at the last measurement (grey once settings change) and blue set-point bugs show the estimate for the current settings. The lamps are Perf, Reliable, UX, Docs and Driver. During the measured run, token tiles leave the device on a belt at a rate that follows tok/s (one tile per 4 tok/s, 2–24 tiles) and the bin counts the 256 output tokens; a new run empties the bin through its floor.

  • Needles = last measured run
  • Blue EST bugs = estimate
  • Tiles = output tokens
  • Lamps = five launch checks
Speed
Open full screen ↗

2Try this

Then change anything yourself.

3Read on ↓

What mastery looks like

Each skill has an artifact that proves it.

Skill What it means technically Artifact that proves it
Diagnose fast tell prefill from decode from memory problems in minutes (Chapters 1–5, 9) the triage checklist below; a benchmark report
Choose the stack match model, format and runtime to the hardware and the audience (Chapters 4–7) a scenario → runtime decision tree
Optimize honestly know which lever moves which number, and by how much before/after tables with the bandwidth bound beside them
Plan an integration turn a goal into milestones and measurable targets the ship plan template
Close the loop turn repeated friction into reproducible, prioritized feedback for tool and model makers an issue with repro, impact and a clear ask
Teach it samples, tutorials and demos that work on day one a sample repo and a launch checklist
Track the field follow model releases, runtime changelogs, Hugging Face trends and community forums a weekly digest of what changed and why it matters

Benchmarking honestly

A benchmark is honest when anyone can repeat it.

  1. Fix the model file and quant (by hash), the prompt set, the context, the output length and the concurrency.
  2. Record the hardware, driver, CUDA, runtime version and power mode. Laptop power limits change results a lot.
  3. Warm up. Repeat at least 5 times. Report the median and p90 (the slowest 1 run in 10 is worse), not the best run.
  4. Report TTFT (time to first token) and decode tok/s separately, with peak VRAM.
  5. Compare with the bandwidth bound (Chapter 3). A result far below ~50% means a wrong setting.

Lab · runs on your own machine

uv run labs/12_benchmark.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 = []
# ///
import _local as local

import json, platform, statistics, subprocess, time

PROMPTS = [
    "Explain why memory bandwidth limits token generation on GPUs.",
    "Write a Python function that parses a GGUF header.",
    "Summarize the trade-offs between DGX Spark and an RTX 5090 for local LLMs.",
]

def benchmark(model, prompts=PROMPTS, repeats=5, num_predict=128):
    local.generate(model, "warm up", num_predict=8)
    runs = []
    for _ in range(repeats):
        for p in prompts:
            r = local.generate(model, p, num_predict=num_predict, temperature=0, seed=1)
            runs.append(r)
    ttft = [r["prefill_s"] * 1000 for r in runs]
    tps = [r["decode_tps"] for r in runs]
    env = {"os": platform.platform(), "machine": platform.machine(), "ollama": local._get("/api/version")["version"], "model": model,
           "digest": next(m["digest"][:12] for m in local.models() if m["name"] == model)}
    q = lambda xs, p: sorted(xs)[min(len(xs) - 1, int(p * len(xs)))]
    report = (f"| metric | median | p90 |\n|---|---:|---:|\n"
              f"| prefill (TTFT excl. queue), ms | {statistics.median(ttft):.0f} | {q(ttft, .9):.0f} |\n"
              f"| decode tok/s | {statistics.median(tps):.1f} | {q(tps, .1):.1f} (p10) |\n")
    return env, report

model = local.need_chat_model()
if model:
    env, report = benchmark(model)
    print(json.dumps(env, indent=1))
    print((report))
Sample output on this course's Mac (Apple M4, 32 GB)
{
 "os": "macOS-26.6.2-arm64-arm-64bit",
 "machine": "arm64",
 "ollama": "0.33.0",
 "model": "qwen3:0.6b",
 "digest": "7df6b6e09427"
}
| metric | median | p90 |
|---|---:|---:|
| prefill (TTFT excl. queue), ms | 26 | 39 |
| decode tok/s | 92.9 | 68.3 (p10) |

A ship plan (template)

A ship plan gives every target a number.

Project:            <app / tool / model>            Type: model | runtime | agent | app
Goal:               e.g. "ship offline mode for 2M Windows users by Q3"
Platform goal:      e.g. "use the GPU-optimized path by default; test on new drivers on day 0"
Hardware targets:   RTX 5060 Ti 16GB … RTX 5090 · RTX 5090 Laptop · RTX PRO 6000 · DGX Spark
Success criteria:   TTFT p50 < 400 ms @ 2k prompt · ≥ 60 tok/s on RTX 5070 Ti · < 10 GB VRAM · 99.5% install success
Milestones:         M1 integration (week 2) · M2 perf parity vs baseline (week 5) · M3 launch readiness review (week 8)
Owners:             eng lead · GPU/runtime expert · product · docs · legal (model licence)
Risks:              model license, driver minimums, laptop power states, Windows VRAM contention
Launch assets:      blog with honest benchmarks, sample repo, demo video, driver/runtime notes, support FAQ
Feedback loop:      bi-weekly sync; blockers → reproducible tickets with impact data

Triage checklist for "it's slow on my GPU"

Ask six questions, in this order.

  1. Does the GPU run the model? Check nvidia-smi and the runtime logs for a CPU fallback or the wrong CUDA build.
  2. Does everything fit? Compare weights + KV + overhead with VRAM (Chapter 4). Look for offload splits.
  3. Is TTFT or tok/s slow? Slow TTFT means prefill and compute. Slow tok/s means decode and bandwidth (Chapters 1 and 3).
  4. Is the quant right for the runtime and GPU? Use Q4_K_M or Q8_0 for GGUF, and FP8 or NVFP4 on Blackwell with TensorRT-LLM.
  5. Check the configuration: context size, KV type, FlashAttention, parallel slots and power mode (Chapter 9).
  6. Is the runtime right for the job? (Chapter 7.)

Your point of view on local AI

Say the case for local AI in 60 seconds.

"AI becomes a feature of every application. More of it will run on the user's own device, for privacy, latency, cost and offline use. Memory and bandwidth limit what runs there. The best ecosystem makes the right model, in the right format, on the right runtime easy to use. Hybrid is the default: small models run locally, big models run in the cloud."

Explain it

  1. "What limits tokens/s for a big model?" Memory bandwidth, because the GPU reads all the weights for every token. A smaller quant raises the speed.
  2. "How do you check that a rival benchmark is fair?" Compare the model file, quant, prompt, context and hardware settings. Then compare both results with the bandwidth bound.
  3. "You have ten optimizations and time for three. Which three?" Run the triage checklist first. Choose the levers that move the bottleneck you measured.

Exercises

  1. Write your own 60-second view on local AI, then cut it to 45 seconds.
  2. Run the benchmark harness on two models and write a one-page report with the bandwidth bound.
  3. Draft the ship plan for a real open-source local AI project.