05 — Phase 0 Projects¶
Three concrete deliverables. Each is small enough to finish in a week of evenings, big enough to be portfolio-worthy. Complete all three before moving to Phase 1’s heavier lifts (though you should be watching Karpathy videos in parallel).
Project 0.1 — Tiled Matmul Speedup (C++)¶
Time budget: 6–10 hours over one week.
Goal: Prove to yourself, with numbers, that the memory hierarchy dominates naïve arithmetic. Establish the “predicted-vs-measured” habit.
Steps:
Write
matmul_naive.cpp: the triple-loop fp32 matmul. Compile with-O3 -march=native -fno-tree-vectorize(disable auto-vectorization to keep the story clean).Write
matmul_tiled.cpp: same signature, tiled withBM=BN=BK=32(fits in L1). Same compile flags.Write
bench.cppthat runs both on 512×512, 1024×1024, 2048×2048 fp32 matrices. Report GFLOPS.Under
perf stat -e cycles,instructions,cache-references,cache-misses,LLC-load-misses, run both. Save the output.Now enable
-O3 -march=nativewith auto-vectorization. Rerun. Note the improvement from SIMD.Compare to a call to
cblas_sgemm(via OpenBLAS). Report the ratio.
Acceptance criteria:
Tiled ≥ 8× naïve on 1024² fp32 (no vectorization).
With
-march=nativevectorization, tiled ≥ 4× the non-vectorized tiled version.Your
bench.cppuses proper warmup and reports median (not mean) of ≥5 runs.perfoutput showsLLC-load-missesdrop by ≥5× from naive to tiled.You’ve predicted each speedup on paper before measuring, and your prediction is within 30% of measured.
README explains WHY each optimization worked in terms of cache-line traffic.
Committed to a public repo with a one-page write-up.
Bonus (do this): port the tiled version to fp16 with _Float16 and measure. Then port to Eigen for comparison. Then, once you’re in Phase 2, rewrite this on the GPU and observe how the same optimizations map.
Project 0.2 — The Numerics Notebook¶
Time budget: 4–6 hours.
Goal: Own the bit-level truth of every floating-point format you’ll encounter for the next three phases.
Steps:
In a Jupyter notebook, use
struct.pack/struct.unpack(Python) orbit_cast(C++) to decode any fp32 into[sign, exponent, mantissa]. Verify withmath.frexp.Implement bf16 as truncated fp32 (drop lower 16 bits of the mantissa — that’s it). Verify against
torch.bfloat16.Implement fp8-e4m3 encode/decode in pure Python. Compare a range of values to what
torch.float8_e4m3fnproduces (PyTorch 2.1+).Implement a symmetric per-channel int8 quantizer for a fp32 weight tensor: compute
scale = max(abs(w)) / 127, quantize, dequantize, report the MSE.Implement a group-wise (group=128) int4 quantizer with one fp16 scale per group. Report MSE. Compare to per-tensor int4 (bad) and per-channel int4.
Chart the smallest positive normal value, largest finite value, and machine epsilon for each format. Print a table.
Then: re-derive online softmax. Implement
softmax_onlinein numpy that consumes one element at a time and matchesscipy.special.softmaxafter the pass. Test with an adversarial input that overflows naive softmax.
Acceptance criteria:
fp8-e4m3 encoder round-trips exactly to
torch.float8_e4m3fnfor at least 100 random fp32 inputs in-range.int8 per-channel quantization MSE < int8 per-tensor MSE on a real weight matrix (grab any Llama tensor).
Online softmax matches
scipy.special.softmaxto within 1e-6 on a test withx = [100, 100.5, -50, 200](where naive softmax overflows).Table of format properties printed and committed as
formats.md.You can, from memory, draw all six format bit-layouts (test yourself weekly).
Project 0.3 — Latency & Bandwidth Atlas of Your Machine¶
Time budget: 3–5 hours.
Goal: Fill in Jeff Dean’s latency table for your actual hardware, so numbers stop being abstract.
Steps:
CPU cache latencies: use
lat_mem_rdfrom lmbench (brew install lmbenchon macOS,apt install lmbenchon Linux) to sweep working-set size from 1 KB to 1 GB. Plot latency vs size. You should see three plateaus (L1, L2, LLC) and a wall (DRAM).CPU memory bandwidth: use STREAM (John McCalpin’s classic: https://www.cs.virginia.edu/stream/) to measure copy/scale/add/triad bandwidth. Compare to your CPU’s spec.
PCIe host↔device bandwidth: use
nvbandwidth(https://github.com/NVIDIA/nvbandwidth) or a simplecudaMemcpybenchmark. Sweep transfer size from 1 KB to 1 GB.GPU HBM bandwidth: run
bandwidthTestfrom CUDA samples, or write a simple copy kernel. Compare to spec (e.g., 3090 = 936 GB/s theoretical, actual ~800–850 GB/s).GPU shared memory bandwidth (optional): run one of the GPU MODE micro-benchmarks (when you get to Phase 2) or a stream benchmark. On an H100 shared memory sustains ~30 TB/s per SM.
Acceptance criteria:
latency_table.mdfilled with your numbers: L1 (~cycles), L2, LLC, DRAM, PCIe small, PCIe large, HBM read, HBM write.STREAM copy bandwidth within 20% of the theoretical peak of your DDR.
cudaMemcpybandwidth reported for pinned vs non-pinned. Difference explained.HBM_write_bandwidth / HBM_read_bandwidthratio noted (usually near 1).The plot of latency vs working-set size shows plateaus at your cache sizes.
Phase 0 Portfolio Manifest¶
At the end of Phase 0, you should have a single public repo (name suggestion: inference-eng-phase0 or your own) with:
01-matmul/
matmul_naive.cpp
matmul_tiled.cpp
bench.cpp
perf_results.txt
README.md
02-numerics/
formats.ipynb
formats.md
softmax_online.py
softmax_tests.py
03-latency-atlas/
latency_table.md
stream_output.txt
bandwidth_plot.png
script.sh
04-cache-lab/ # from Section 01
parts_A_and_B/
05-notes/
brrr_notes.md # Your notes on Horace He's essay
csapp_ch5_ch6_notes.md
bit_layouts.md # The drill drawings, dated to prove weekly practice
A README at the root ties them together and links to your public write-ups (Twitter/X, blog, or GitHub). A single tweet-thread walking through the matmul speedup and roofline analysis usually generates useful feedback from GPU MODE folks.
The Predicted-vs-Measured Lab Notebook¶
Start your habitat now, before Phase 1 raises the stakes. A simple Markdown file, one entry per experiment:
## 2026-07-05: naive matmul on 1024x1024 fp32
**Hypothesis:** Naive matmul is dominated by L2/LLC misses on B (column-major access).
**Predicted GFLOPS:** ~1.5 (assuming ~200 ns DRAM latency dominates every K iteration).
**Measured GFLOPS:** 1.8.
**Gap:** Prediction slightly pessimistic; hardware prefetcher is catching some of the accesses.
**Nsight/perf evidence:** `LLC-load-misses` = 4.2e7; `cache-references` = 2.1e9; miss rate 2%.
**Next action:** Tile with BM=BN=BK=32; predict 15–20× speedup from working-set fitting L1.
Every experiment. Every phase. This notebook is what you’ll show at your first onsite study.