"""Step 6.8 (D-029): the results tables, rendered from one benchmark run's JSON files. scripts/bench_all.py runs every benchmark in BENCHES into one directory (runs/bench/v1b/), then calls render_results() for docs/RESULTS.md and render_readme() for the README's blocks. Everything here is pure (JSON in, markdown out), so the CPU tests cover it without a GPU. Rules this module enforces: - every table is built from one run directory: nothing is mixed in from other sessions - a missing file is an error naming the `--only` command that makes it, never an empty table - each table carries a heat line from its files' provenance, since absolute tok/s on this laptop moves 10-20% between sessions with temperature """ from __future__ import annotations import json import re from dataclasses import dataclass from pathlib import Path # A sustained clock below this, or a hardware slowdown, means the numbers are not this GPU's normal # (11-04 runs: busy median ~1,600 MHz at 85 °C, with sw_power_cap / sw_thermal_slowdown only). MIN_CLOCK_MHZ = 1400 HARD_THROTTLE = {"hw_slowdown", "hw_thermal_slowdown", "hw_power_brake_slowdown"} PAGED_ROW = ("paged/16", 32) # the README's paged row: 16-token blocks, 32 slots, the 8-slot budget @dataclass(frozen=True) class Bench: name: str script: str args: tuple[str, ...] out: str # path inside the run directory minutes: int # rough, for the run plan printout env: tuple[tuple[str, str], ...] = () # extra environment for this benchmark, recorded in the manifest @property def out_dir_flag(self) -> bool: # profile_decode takes a directory, the others a file return self.script == "profile_decode.py" TL = ("--model", "tinyllama") # The naive engine runs a full forward per token, so its activations and logits grow by one position every # step. No cached block fits the next request, the caching allocator keeps reserving new ones until the GPU # is full (7,991 MiB in use for a 475 MiB peak at 30M B=32 P=128), and a cuBLAS call then fails # (6.8, 2026-11-06). Expandable segments let a block grow in place: 695 MiB, same speed. Only the naive # benchmarks need it; the other engines use fixed-size caches. GROWING = (("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True"),) BENCHES = [ Bench("30m-naive", "bench_generate.py", ("--engine", "naive", "--dtype", "bf16"), "30m/naive.json", 4, GROWING), Bench("30m-kv", "bench_generate.py", ("--engine", "kv", "--dtype", "bf16"), "30m/kv.json", 2), Bench("30m-kv-graph", "bench_generate.py", ("--engine", "kv-graph", "--dtype", "bf16"), "30m/kv-graph.json", 2), Bench("30m-serving", "bench_serving.py", ("--cuda-graph", "--paged"), "30m/serving.json", 12), Bench("30m-int8", "bench_quant.py", (), "30m/int8.json", 4), Bench("30m-profile", "profile_decode.py", ("--batch", "1"), "30m/profile/decode_main_B1.json", 1), Bench("tl-logits", "tinyllama_vs_hf.py", (), "tinyllama/logits_vs_hf.json", 4), Bench("tl-hf", "bench_generate.py", (*TL, "--engine", "hf"), "tinyllama/hf.json", 12), Bench("tl-naive", "bench_generate.py", (*TL, "--engine", "naive"), "tinyllama/naive.json", 6, GROWING), Bench("tl-kv", "bench_generate.py", (*TL, "--engine", "kv"), "tinyllama/kv.json", 10), Bench("tl-kv-graph", "bench_generate.py", (*TL, "--engine", "kv-graph"), "tinyllama/kv-graph.json", 6), Bench("tl-serving", "bench_serving.py", (*TL, "--cuda-graph"), "tinyllama/serving.json", 15), Bench("tl-int8", "bench_quant.py", TL, "tinyllama/int8.json", 10), # 4.10b: the Triton INT8 kernel, end to end with CUDA graphs, and its matmuls alone Bench("tl-int8-graph", "bench_quant.py", (*TL, "--engine", "kv-graph", "--triton"), "tinyllama/int8_graph_triton.json", 12), Bench("tl-int8-gemv", "bench_int8_gemv.py", (), "tinyllama/int8_gemv.json", 3), # 4.10a: the Triton RMSNorm + residual add, alone and end to end with CUDA graphs Bench("tl-rmsnorm", "bench_rmsnorm.py", (), "tinyllama/rmsnorm_graph.json", 10), Bench("tl-profile", "profile_decode.py", (*TL, "--batch", "1"), "tinyllama/profile/decode_tinyllama_B1.json", 2), ] BY_NAME = {b.name: b for b in BENCHES} MANIFEST = "manifest.json" # Optional results run by hand, read when present (None otherwise). 4.10c: tl-logits with our RMSNorm forced # to the PyTorch code (tinyllama_vs_hf.py --norm-backend torch), which shows where D-032's logit gap comes from. EXTRAS = {"tl-logits-torchnorm": "tinyllama/logits_vs_hf_torchnorm.json"} class MissingResult(Exception): pass def missing(run_dir: str | Path) -> list[Bench]: return [b for b in BENCHES if not (Path(run_dir) / b.out).exists()] def load_run(run_dir: str | Path) -> dict: """{bench name: its JSON, "manifest": manifest or {}}; raises MissingResult listing every gap.""" run_dir = Path(run_dir) gaps = missing(run_dir) if gaps: lines = "\n".join(f" {run_dir / b.out} -> scripts/bench_all.py --only {b.name}" for b in gaps) raise MissingResult(f"{len(gaps)} result file(s) missing from {run_dir}:\n{lines}") data = {b.name: json.loads((run_dir / b.out).read_text()) for b in BENCHES} for name, out in EXTRAS.items(): data[name] = json.loads((run_dir / out).read_text()) if (run_dir / out).exists() else None m = run_dir / MANIFEST data["manifest"] = json.loads(m.read_text()) if m.exists() else {} return data # ---------------------------------------------------------------- formatting def n(x: float, d: int = 0) -> str: return f"{x:,.{d}f}" def table(header: list[str], rows: list[list[str]]) -> str: out = ["| " + " | ".join(header) + " |", "|" + "---|" * len(header)] out += ["| " + " | ".join(r) + " |" for r in rows] return "\n".join(out) def row(rows: list[dict], **kv) -> dict | None: return next((r for r in rows if all(r.get(k) == v for k, v in kv.items())), None) def need(rows: list[dict], what: str, **kv) -> dict: r = row(rows, **kv) if r is None or "skipped" in r: raise MissingResult(f"{what}: no row with {kv}") return r # ---------------------------------------------------------------- heat def heat(prov: dict | None) -> dict | None: """Busy SM clock median / min, peak temperature, throttle reasons (gpu_idle dropped), warnings.""" if not prov or not prov.get("during"): return None d = prov["during"] clock = d.get("clocks.sm_busy") or d.get("clocks.sm") or {} reasons = [r for r in d.get("throttle_reasons_seen") or [] if r != "gpu_idle"] warn = sorted(HARD_THROTTLE.intersection(reasons)) # A low clock alone is not throttling: small or host-bound steps leave the GPU idle and it clocks down by # itself (30M KV decode: 1,095 MHz at 65 °C). Flag it only when the driver also reports a thermal slowdown. if (clock.get("median") is not None and clock["median"] < MIN_CLOCK_MHZ and "sw_thermal_slowdown" in reasons): warn.append(f"busy clock median {clock['median']:.0f} MHz < {MIN_CLOCK_MHZ}") return {"clock_median": clock.get("median"), "clock_min": clock.get("min"), "temp_max": (d.get("temperature.gpu") or {}).get("max"), "reasons": reasons, "warn": warn} def heat_text(h: dict | None) -> str: if h is None: return "not recorded" parts = [] if h["clock_median"] is not None: parts.append(f"{n(h['clock_median'])} MHz (min {n(h['clock_min'])})") if h["temp_max"] is not None: parts.append(f"{h['temp_max']:.0f} °C") parts.append(" + ".join(h["reasons"]) or "no throttling") return ("⚠ " if h["warn"] else "") + ", ".join(parts) def heat_line(data: dict, names: list[str], labels: list[str] | None = None) -> str: labels = labels or names body = "; ".join(f"{lab}: {heat_text(heat(data[nm].get('provenance')))}" for nm, lab in zip(names, labels)) return f"_Heat (busy SM clock median, peak GPU temperature, throttle reasons): {body}._" def warnings(data: dict) -> list[str]: out = [] for b in BENCHES: h = heat(data[b.name].get("provenance")) if h and h["warn"]: out.append(f"{b.name}: {', '.join(h['warn'])} -> consider scripts/bench_all.py --only {b.name}") return out # ---------------------------------------------------------------- tables def ceiling_note(d: dict) -> str: bw, wb = d["bandwidth_bytes_s"], d["weight_bytes"] return (f"{bw / 1e9:.0f} GB/s ÷ {n(wb / 2**20)} MiB = **{n(bw / wb)} tok/s per sequence**") def repeats_note(d: dict) -> str: r = (d.get("provenance") or {}).get("repeats") return f"median of {r} runs" if r else "median of repeated runs" TL_ENGINES = [("tl-hf", "HF Transformers `generate` (bf16, SDPA)"), ("tl-naive", "Ours, naive (full forward per token)"), ("tl-kv", "Ours, KV cache"), ("tl-kv-graph", "Ours, KV cache + CUDA graphs")] def tl_b1_table(data: dict, prompt_len: int = 128) -> str: rows = [] for name, label in TL_ENGINES: r = need(data[name]["rows"], name, batch=1, prompt_len=prompt_len) cells = [label, n(r["decode_tok_s"]), f"{r['pct_of_ceiling']:.0f}%", f"{r['ms_per_step']:.1f}"] rows.append([f"**{c}**" for c in cells] if name == "tl-kv-graph" else cells) return table(["Engine", "tok/s", "% of ceiling", "ms / step"], rows) def decode_grid(data: dict, engines: list[tuple[str, str]]) -> str: plens = sorted({r["prompt_len"] for nm, _ in engines for r in data[nm]["rows"]}) out, slides = [], False for name, label in engines: rows = data[name]["rows"] for b in sorted({r["batch"] for r in rows}): cells = [label, str(b)] for p in plens: r = row(rows, batch=b, prompt_len=p) if r is None or "skipped" in r: cells.append("—") continue mark = " ‡" if r.get("window_slides") else "" slides |= bool(mark) cells.append(f"{n(r['decode_tok_s'])} ({r['pct_of_ceiling']:.0f}%){mark}") out.append(cells) text = table(["Engine", "Batch", *[f"P={p}" for p in plens]], out) text += "\n\nDecode tok/s, with the % of the bandwidth ceiling at that batch size. — = skipped: " \ "prompt + new tokens exceed the context, which a KV cache can't slide." if slides: text += " ‡ = prompt + new tokens exceed the context, so naive slides its window." return text def int8_section(d: dict) -> str: base = d["baseline"] variants = list(dict.fromkeys(r["variant"] for r in d["rows"])) others = [v for v in variants if v != base] rows = [] for b in sorted({r["batch"] for r in d["rows"]}): tok = {v: need(d["rows"], "int8", variant=v, batch=b)["decode_tok_s"] for v in variants} rows.append([str(b), *[n(tok[v]) for v in variants], *[f"{tok[v] / tok[base]:.2f}×" for v in others]]) text = table(["Batch", *[f"{v} tok/s" for v in variants], *[f"{v} / {base}" for v in others]], rows) mib = {v: sum(d["weights_mib"][v].values()) for v in variants} loss, tf, same = d["val_loss"], d["teacher_forced"], d["greedy_rows_identical"] n_seq = same[base] text += "\n\n" + "\n".join([ "- Weights: " + ", ".join(f"{v} {n(mib[v])} MiB" for v in variants) + ".", "- Val loss: " + ", ".join(f"{v} {loss[v]:.4f}" for v in variants) + " (" + ", ".join(f"{v} {loss[v] - loss[base]:+.4f}" for v in others) + f" vs {base}).", f"- Teacher-forced on {base}'s greedy tokens: argmax agreement " + ", ".join(f"{v} {tf[v]['argmax_agree']:.2%}" for v in variants) + f" (median top-1 margin {tf['_median_gap']:.2f}).", f"- Greedy rows identical to {base}: " + ", ".join(f"{v} {same[v]}/{n_seq}" for v in variants) + ".", ]) return text GEMV_VARIANTS = [("bf16", "bf16"), ("torch", "PyTorch int8"), ("triton", "Triton int8")] def gemv_bandwidth(d: dict, m: int, v: str) -> tuple[float, float]: """(bytes one decode step's matmuls must read, % of the measured bandwidth) for variant v at m rows.""" rows = [r for r in d["rows"] if r["m"] == m] nbytes = sum(r[f"{v}_gb_s"] * r[f"{v}_us"] * 1e3 * r["per_step"] for r in rows) # GB/s × µs = kB return nbytes, 100 * nbytes / (d["step_us"][str(m)][v] * 1e-6) / d["bandwidth"] def gemv_table(d: dict) -> str: ms = sorted(map(int, d["step_us"])) rows = [] for m in ms: s = d["step_us"][str(m)] rows.append([str(m), *[f"{s[v] / 1e3:.2f}" for v, _ in GEMV_VARIANTS], _ratio(s["bf16"] / s["triton"]), _ratio(s["torch"] / s["triton"], 1)]) text = table(["Rows (batch)", *[f"{lab} ms" for _, lab in GEMV_VARIANTS], "Triton vs bf16", "Triton vs PyTorch int8"], rows) (b_bf, p_bf), (b_tr, p_tr) = gemv_bandwidth(d, ms[0], "bf16"), gemv_bandwidth(d, ms[0], "triton") calls = sum(r["per_step"] for r in d["rows"] if r["m"] == ms[0]) return text + ( f"\n\nTime for every matmul of one decode step (the {calls} Linears), replayed in CUDA graphs over weight " f"copies larger than the L2 cache, so each call reads its weight from memory. At {ms[0]} row, a step must " f"read {n(b_bf / 2**20)} MiB of bf16 weights or {n(b_tr / 2**20)} MiB of int8 weights and scales: bf16 " f"runs at {p_bf:.0f}% of the measured {d['bandwidth'] / 1e9:.0f} GB/s, the Triton kernel at {p_tr:.0f}%. " "PyTorch int8 casts each weight back to bf16 first, so it moves more bytes than bf16, not fewer.") def rmsnorm_section(d: dict) -> str: batches = sorted({r["batch"] for r in d["rows"]}) rows = [] for b in batches: t, k = need(d["rows"], "rmsnorm", variant="torch", batch=b), need(d["rows"], "rmsnorm", variant="triton", batch=b) rows.append([str(b), n(t["decode_tok_s"]), n(k["decode_tok_s"]), _ratio(k["decode_tok_s"] / t["decode_tok_s"]), f"{t['ms_per_step']:.2f} → {k['ms_per_step']:.2f}"]) text = table(["Batch", "PyTorch norm tok/s", "Triton norm tok/s", "Triton vs PyTorch", "ms per step"], rows) p, tf, alone = d["profile_b1"], d["teacher_forced"], d["kernels_alone"] fused = sorted((r for r in alone if r["op"] == "add+norm"), key=lambda r: r["rows"]) if not fused or fused[0]["rows"] != 1: raise MissingResult("rmsnorm alone: no add+norm row with 1 row") one, big = fused[0], fused[-1] return text + ( f"\n\nKV cache + CUDA graphs, the two norms alternating at each batch size. At batch 1 a decode step runs " f"{n(p['torch']['gpu_ops_per_step'])} GPU ops with the PyTorch norm and {n(p['triton']['gpu_ops_per_step'])} " f"with the Triton kernels (GPU busy {p['torch']['gpu_busy_ms_per_step']:.2f} → " f"{p['triton']['gpu_busy_ms_per_step']:.2f} ms per step). Residual add + norm alone: one row takes " f"{one['torch_us']:.1f} µs in PyTorch and {one['triton_us']:.1f} µs in Triton; {n(big['rows'])} rows run at " f"{big['triton_pct_bw']:.0f}% of the measured {d['bandwidth'] / 1e9:.0f} GB/s. Greedy rows identical to the " f"PyTorch norm: {d['greedy_rows_identical']}/{d['n_greedy_rows']}; teacher-forced argmax agreement " f"{tf['argmax_agree']:.2%}.") def serving_baseline(rows: list[dict], r: dict) -> tuple[dict, str] | None: """Static has none; continuous vs static at the same size; graphs vs continuous; paged vs 8 contiguous slots.""" e, s = r["engine"], r["size"] if e == "continuous": return row(rows, engine="static", size=s), "static" if e == "cont+graph": return row(rows, engine="continuous", size=s), "continuous" if e.startswith("paged/"): return row(rows, engine="continuous", size=8), "continuous 8 (same KV memory)" return None def serving_table(d: dict) -> str: rows = [] for r in d["rows"]: base = serving_baseline(d["rows"], r) vs = f"{r['tok_s'] / base[0]['tok_s']:.2f}× {base[1]}" if base and base[0] else "—" alloc = r.get("kv_alloc_used_mean") c = r.get("correctness") or {} rows.append([r["engine"], str(r["size"]), n(r["tok_s"]), vs, f"{r['latency_mean_s']:.2f} / {r['latency_p95_s']:.2f}", f"{r['ttft_mean_s']:.2f} / {r['ttft_p95_s']:.2f}", f"{r['kv_reserved_mib']:.0f}", "—" if alloc is None else f"{alloc:.0%}", str(r["peak_active"] or "—"), str(r["preemptions"]), f"{c['identical']}/{c['of']}" if c else "—"]) w = d["workload"] text = table(["Engine", "Slots", "tok/s", "vs", "Latency mean / p95 (s)", "TTFT mean / p95 (s)", "KV MiB", "KV claimed in use", "Peak active", "Preempt", "Same as solo"], rows) reps = (d.get("provenance") or {}).get("repeats") text += (f"\n\n{w['requests']} requests, all queued at t=0 (seed {w['seed']}); " f"{'median-wall run of ' + str(reps) if reps else 'median run'}. \"Same as solo\": requests whose " "tokens equal solo `generate()`; bf16 near-ties flip some, so it is reported, not asserted.") return text def logits_section(d: dict, torchnorm: dict | None = None) -> str: """`torchnorm`: the same run with our RMSNorm forced to PyTorch (EXTRAS); adds its rows, labelled by norm.""" rows = [] runs = [(d, "")] if torchnorm is None else [ (d, f", {(d.get('provenance') or {}).get('norm_backend', '?')} norm"), (torchnorm, ", torch norm")] for (r, label), phase in [(x, p) for x in runs for p in ("prefill", "decode")]: s = r["stats"][phase] flips = s["flips"]["ours_bf16"] rows.append([phase + label, str(s["ours_vs_hf_bf16"]["positions"]), "yes" if s["bit_identical_to_hf_bf16"] else "no", f"{s['ours_vs_hf_bf16']['max_abs']:.3g}", f"{s['ours_bf16']['top1']:.2%}", f"{s['hf_bf16']['top1']:.2%}", ", ".join(f"{f:.3f}" for f in flips) or "none"]) text = table(["Phase", "Positions", "Ours == HF bf16 (`torch.equal`)", "Max abs Δ, ours vs HF", "Top-1 vs fp32: ours", "Top-1 vs fp32: HF", "Our margin at each fp32 flip"], rows) div = d["greedy_divergence"] same = sum(g["ours_vs_hf_bf16"] is None for g in div) at = lambda i: "never" if i is None else f"token {i}" # noqa: E731 text += (f"\n\nGreedy, {len(div)} prompts × {d['new_tokens']} tokens: ours equals HF bf16 on {same}/{len(div)}. " "First divergence from the fp32 greedy text, ours / HF: " + "; ".join(f"{at(g['ours_vs_fp32'])} / {at(g['hf_bf16_vs_fp32'])}" for g in div) + ".") return text def profile_table(profiles: list[tuple[str, dict]]) -> str: rows = [] for label, d in profiles: for mode in ("eager", "graph"): m = d["modes"][mode] rows.append([label, mode, f"{m['ms_per_step']:.2f}", f"{m['host_issue_ms_per_step']:.2f}", f"{m['gpu_busy_ms_per_step']:.2f}", f"{m['gpu_idle_frac']:.0%}", n(m["decode_tok_s"]), f"{m['pct_of_ceiling']:.0f}%"]) return table(["Model (B=1)", "Decode step", "ms / step", "Host issue ms", "GPU busy ms", "GPU idle", "tok/s", "% of ceiling"], rows) # ---------------------------------------------------------------- pages def run_header(data: dict) -> str: m = data["manifest"] rec = m.get("benches") or {} # the Run line describes the main run only; --only reruns get their own line, grouped by session provs = [data[b.name].get("provenance") or {} for b in BENCHES if not (rec.get(b.name) or {}).get("rerun")] commits = sorted({(p.get("git") or {}).get("commit") or "?" for p in provs}) dirty = any((p.get("git") or {}).get("dirty") for p in provs) first = next((p for p in provs if p.get("gpu_name")), {}) times = sorted(p["time_utc"] for p in provs if p.get("time_utc")) commit = (f"commit `{commits[0][:7]}`" if len(commits) == 1 else f"⚠ {len(commits)} commits ({', '.join(c[:7] for c in commits)})") lines = [ f"- **Run:** {times[0] if times else '?'} → {m.get('finished_utc', times[-1] if times else '?')} UTC, " f"{commit}{' with uncommitted changes' if dirty else ''}, NitroSense **{m.get('power_mode', 'not recorded')}**.", f"- **Machine:** {first.get('gpu_name', '?')}, driver {first.get('driver', '?')}, " f"torch {first.get('torch', '?')}, CUDA {first.get('cuda', '?')}.", ] sessions: dict[tuple, list[str]] = {} for n, r in sorted(rec.items(), key=lambda kv: str(kv[1].get("finished_utc", ""))): if r.get("rerun"): fin = str(r.get("finished_utc", "?")) key = (fin[:10], ((r.get("git") or {}).get("commit") or "?")[:7], r.get("power_mode") or "not recorded") sessions.setdefault(key, []).append( f"{n} (finished {fin[11:16]} UTC, started at {r.get('start_temp_c', '?')} °C)") if sessions: lines.append("- **Rerun later with `--only`:** " + "; ".join( f"{day}, commit `{c}`, NitroSense **{mode}**: {', '.join(names)}" for (day, c, mode), names in sessions.items()) + ". Every other benchmark is from the run above.") return "\n".join(lines) def render_results(data: dict, run_dir: str = "runs/bench/v1b") -> str: tl_gen = [(nm, lab) for nm, lab in TL_ENGINES] w = warnings(data) parts = [ "# Results", "", f"", "", "Every number on this page comes from **one** run of `scripts/bench_all.py --run`: one commit, " "one session, one GPU job at a time, with a cool-down before each benchmark" + (" (a benchmark rerun later with `--only` is listed below)." if any( r.get("rerun") for r in (data["manifest"].get("benches") or {}).values()) else "."), "", run_header(data), "- **How to read it:** ratios between rows of one table are the reliable result; they share a " "session and its heat. Absolute tok/s on this laptop moves 10–20% between sessions with " "temperature, so each table ends with its heat line. ⚠ marks a hardware slowdown, or a thermal " f"slowdown with the busy clock median below {n(MIN_CLOCK_MHZ)} MHz.", "- **Elsewhere:** dated results and the reasoning behind each step are in " "[INFERENCE.md](INFERENCE.md); ours vs vLLM vs llama.cpp under load (4.8a, rerun in 4.10c) is in " "[SERVING.md](SERVING.md).", "- **Reproduce:** `uv run python scripts/bench_all.py` re-renders this page and the README " "tables from the saved JSON in seconds, with no GPU. `--run --power-mode ` reruns " "everything (~1.5–2 h); `--only ` reruns one benchmark.", ] if w: parts += ["", "**⚠ Throttling warnings:**", *[f"- {x}" for x in w]] hf = data["tl-hf"] parts += [ "", "## TinyLlama 1.1B (bf16)", "", "### Decode at batch 1", "", f"128-token prompt, 256 new tokens, {repeats_note(hf)}. Ceiling: {ceiling_note(hf)}.", "", tl_b1_table(data), "", heat_line(data, [n_ for n_, _ in tl_gen], ["hf", "naive", "kv", "kv-graph"]), "", img("img/decode_b1.svg", b1_alt(data)), "", "### Decode by batch size and prompt length", "", decode_grid(data, tl_gen), "", img("img/batch_scaling.svg", scaling_alt(data)), "", "", "### INT8 weights (per-row scales)", "", "Two ways to run an int8 matmul: `int8` casts the weight back to bf16 on every call (plain PyTorch); " "`int8-triton` is our Triton kernel, which reads the int8 weight and dequantizes in registers (4.10b).", "", "#### Eager decode (KV cache, no graphs)", "", int8_section(data["tl-int8"]), "", heat_line(data, ["tl-int8"]), "", "#### With CUDA graphs: PyTorch path vs Triton kernel", "", "Each variant is timed with only its own weights on the GPU.", "", int8_section(data["tl-int8-graph"]), "", heat_line(data, ["tl-int8-graph"]), "", "#### The matmuls alone", "", gemv_table(data["tl-int8-gemv"]), "", heat_line(data, ["tl-int8-gemv"]), "", "### Fused RMSNorm + residual add (Triton, 4.10a)", "", rmsnorm_section(data["tl-rmsnorm"]), "", heat_line(data, ["tl-rmsnorm"]), "", "### Serving: static vs continuous batching, with and without CUDA graphs", "", serving_table(data["tl-serving"]), "", heat_line(data, ["tl-serving"]), "", "### Logits vs HF Transformers", "", "Ours (bf16) and HF Transformers (bf16, eager) against an fp32 HF reference, on the same prompts.", "", logits_section(data["tl-logits"], data.get("tl-logits-torchnorm")), "", heat_line(data, ["tl-logits"]), "", "## Our 30M model (bf16 weights)", "", "### Decode by batch size and prompt length", "", f"256 new tokens, {repeats_note(data['30m-kv'])}. Ceiling: {ceiling_note(data['30m-kv'])}.", "", decode_grid(data, [("30m-naive", "Naive"), ("30m-kv", "KV cache"), ("30m-kv-graph", "KV + CUDA graphs")]), "", heat_line(data, ["30m-naive", "30m-kv", "30m-kv-graph"], ["naive", "kv", "kv-graph"]), "", "### Serving: static, continuous, CUDA graphs, paged KV", "", "Paged rows: `paged/`, all within the 8-slot contiguous cache's KV memory.", "", serving_table(data["30m-serving"]), "", heat_line(data, ["30m-serving"]), "", img("img/serving.svg", serving_alt(data)), "", "### INT8 weights", "", int8_section(data["30m-int8"]), "", heat_line(data, ["30m-int8"]), "", "## Decode-step profile (batch 1, 128-token context)", "", profile_table([("TinyLlama 1.1B", data["tl-profile"]), ("30M", data["30m-profile"])]), "", heat_line(data, ["tl-profile", "30m-profile"], ["TinyLlama", "30M"]), "", img("img/decode_profile.svg", profile_alt(data)), ] return re.sub(r"\n{3,}", "\n\n", "\n".join(parts)) + "\n" # ---------------------------------------------------------------- README blocks def _ratio(x: float, d: int = 2) -> str: return f"{x:.{d}f}×" # ---------------------------------------------------------------- plot embeds (6.2, D-030) # The SVGs come from tinyllm.plots via bench_all.py; the alt text carries the plot's numbers. def img(path: str, alt: str) -> str: return f"![{alt}]({path})" def _tl(data: dict, nm: str, b: int) -> dict: return need(data[nm]["rows"], nm, batch=b, prompt_len=128) def b1_alt(data: dict) -> str: hf = data["tl-hf"] return ("Bar chart, TinyLlama 1.1B decode at batch 1: " + "; ".join( f"{lab.split(' (')[0].replace('`', '')} {n(_tl(data, nm, 1)['decode_tok_s'])} tok/s" for nm, lab in TL_ENGINES) + f"; bandwidth ceiling {n(hf['bandwidth_bytes_s'] / hf['weight_bytes'])} tok/s") def scaling_alt(data: dict) -> str: bs = sorted({r["batch"] for r in data["tl-kv-graph"]["rows"]} & {r["batch"] for r in data["tl-hf"]["rows"]}) per = lambda nm: ", ".join(n(_tl(data, nm, b)["decode_tok_s"]) for b in bs) # noqa: E731 return (f"Line chart, decode tok/s at batch {', '.join(map(str, bs))}: ours with CUDA graphs {per('tl-kv-graph')}; " f"HF Transformers {per('tl-hf')}; log scales, with the bandwidth ceiling times batch size") def serving_alt(data: dict) -> str: tls, ms = data["tl-serving"]["rows"], data["30m-serving"]["rows"] tok = lambda rows, nm, **kv: n(need(rows, nm, **kv)["tok_s"]) # noqa: E731 pg = need(ms, "30m-serving", engine=PAGED_ROW[0], size=PAGED_ROW[1]) return ("Bar chart, serving tok/s. TinyLlama, 8 slots: static " + tok(tls, "tl-serving", engine="static", size=8) + ", continuous " + tok(tls, "tl-serving", engine="continuous", size=8) + ", continuous with CUDA graphs " + tok(tls, "tl-serving", engine="cont+graph", size=8) + ". 30M model, same KV memory: contiguous 8 sequences " + tok(ms, "30m-serving", engine="continuous", size=8) + f", paged {pg['peak_active']} sequences {n(pg['tok_s'])}") def profile_alt(data: dict) -> str: m = data["tl-profile"]["modes"] return ("Bar chart, share of a TinyLlama decode step the GPU is idle: " f"eager {m['eager']['gpu_idle_frac']:.0%} ({m['eager']['ms_per_step']:.1f} ms per step), " f"CUDA graph {m['graph']['gpu_idle_frac']:.0%} ({m['graph']['ms_per_step']:.1f} ms per step)") def readme_blocks(data: dict) -> dict[str, str]: g = {nm: data[nm]["rows"] for nm, _ in TL_ENGINES} at = lambda nm, b: need(g[nm], nm, batch=b, prompt_len=128) # noqa: E731 hf1, kv1, kvg1 = at("tl-hf", 1), at("tl-kv", 1), at("tl-kv-graph", 1) tls, ms = data["tl-serving"]["rows"], data["30m-serving"]["rows"] st8 = need(tls, "tl-serving", engine="static", size=8) co8 = need(tls, "tl-serving", engine="continuous", size=8) gr8 = need(tls, "tl-serving", engine="cont+graph", size=8) c8 = need(ms, "30m-serving", engine="continuous", size=8) pg = need(ms, "30m-serving", engine=PAGED_ROW[0], size=PAGED_ROW[1]) lg = data["tl-logits"]["stats"] identical = lg["prefill"]["bit_identical_to_hf_bf16"] and lg["decode"]["bit_identical_to_hf_bf16"] max_d = max(lg[p]["ours_vs_hf_bf16"]["max_abs"] for p in ("prefill", "decode")) positions = sum(lg[p]["ours_vs_hf_bf16"]["positions"] for p in ("prefill", "decode")) flips = [f for p in ("prefill", "decode") for f in lg[p]["flips"]["ours_bf16"]] top1 = {p: lg[p]["ours_vs_hf_bf16"]["top1"] for p in ("prefill", "decode")} tn = (data.get("tl-logits-torchnorm") or {}).get("stats") torch_identical = bool(tn) and all(tn[p]["bit_identical_to_hf_bf16"] for p in ("prefill", "decode")) gap_cause = (" The difference is our Triton RMSNorm (D-032): with the PyTorch norm the logits are " "bit-identical to HF's." if torch_identical else "") logits_hl = ("**Our engine's logits are bit-identical to HF Transformers** in bf16 on TinyLlama, " "prefill and cached decode." if identical else f"**Our engine's bf16 logits match HF Transformers to within {max_d:.2g}** on TinyLlama " f"(same top token at {top1['prefill']:.1%} of prefill and {top1['decode']:.1%} of cached-decode " "positions)." + gap_cause) highlights = "\n".join([ img("docs/img/decode_b1.svg", b1_alt(data)), "", f"- **TinyLlama 1.1B decodes at {n(kvg1['decode_tok_s'])} tok/s at batch 1: " f"{kvg1['pct_of_ceiling']:.0f}% of the bandwidth ceiling, " f"{_ratio(kvg1['decode_tok_s'] / hf1['decode_tok_s'], 1)} HF `generate`** (KV cache + CUDA graphs).", f"- {logits_hl}", f"- **Serving:** continuous batching gives {_ratio(co8['tok_s'] / st8['tok_s'])} static batching. " f"A paged KV cache fits {pg['peak_active']} sequences where contiguous slots fit 8, for " f"{_ratio(pg['tok_s'] / c8['tok_s'])} the throughput in the same memory.", ]) hf = data["tl-hf"] prof = data["tl-profile"]["modes"] kv32 = at("tl-kv", 32) host_bound = prof["eager"]["gpu_idle_frac"] >= 0.5 i8 = data["tl-int8-graph"] vs_bf16 = lambda v, b: (need(i8["rows"], "tl-int8-graph", variant=v, batch=b)["decode_tok_s"] # noqa: E731 / need(i8["rows"], "tl-int8-graph", variant="bf16", batch=b)["decode_tok_s"]) tri, cast = {b: vs_bf16("int8-triton", b) for b in (1, 32)}, {b: vs_bf16("int8", b) for b in (1, 32)} int8_text = (f"- **INT8 weights:** val loss {i8['val_loss']['int8-triton'] - i8['val_loss']['bf16']:+.4f}, " f"{i8['teacher_forced']['int8-triton']['argmax_agree']:.1%} argmax agreement with bf16. Our " "Triton kernel reads the int8 weight and dequantizes inside the matmul; with CUDA graphs, decode " f"is {_ratio(tri[1])} bf16 at batch 1 and {_ratio(tri[32])} at batch 32. Casting the weight back " f"to bf16 on every call (plain PyTorch) gives {_ratio(cast[1])} and {_ratio(cast[32])}") int8_text += (": it moves more bytes than bf16, not fewer." if cast[1] < 1 and cast[32] < 1 else ".") eager_text = (f"- **Eager decode is {'host-bound' if host_bound else 'slow'}.** At batch 1, HF and our eager " f"engine take {hf1['ms_per_step']:.1f} and {kv1['ms_per_step']:.1f} ms per step " f"(ours {kv32['ms_per_step']:.1f} ms at batch 32). The profiler shows the GPU idle " f"{prof['eager']['gpu_idle_frac']:.0%} of an eager step" + (", waiting for kernel launches" if host_bound else "") + f". Capturing the decode step as a CUDA graph removes that overhead; the GPU is then idle " f"{prof['graph']['gpu_idle_frac']:.0%} of the step.") corr = (f"- **Correctness:** bf16 logits equal HF's bf16 logits exactly (`torch.equal`) at all {positions} " "test positions." if identical else f"- **Correctness:** bf16 logits differ from HF's bf16 logits by at most {max_d:.2g} over " f"{positions} test positions." + gap_cause) corr += (f" Every top-1 disagreement with fp32 is a near-tie (margin ≤ {max(flips):.3f} logits)." if flips else " No top-1 disagreement with fp32.") tinyllama = "\n".join([ f"Decode speed at batch 1 with a 128-token prompt, 256 new tokens, {repeats_note(hf)}. The ceiling is " f"the measured bandwidth divided by the weight size: {ceiling_note(hf)}, because each decoded token " "has to read every weight once.", "", tl_b1_table(data), "", eager_text, f"- **Batching:** with graphs, {n(at('tl-kv-graph', 8)['decode_tok_s'])} tok/s at batch 8 and " f"{n(at('tl-kv-graph', 32)['decode_tok_s'])} tok/s at batch 32 (HF: {n(at('tl-hf', 8)['decode_tok_s'])} " f"and {n(at('tl-hf', 32)['decode_tok_s'])}).", int8_text, corr, "", img("docs/img/batch_scaling.svg", scaling_alt(data)), ]) w = data["tl-serving"]["workload"] reps = (data["tl-serving"].get("provenance") or {}).get("repeats") serving = "\n".join([ f"{w['requests']} requests of mixed lengths ({n(st8['useful_tokens'])} tokens to generate), " f"{'median of ' + str(reps) + ' runs' if reps else 'median run'}.", "", table(["Scheduler / cache", "Setting", "tok/s", "vs baseline"], [ ["Static batching (TinyLlama)", "8 slots", n(st8["tok_s"]), "—"], ["Continuous batching (TinyLlama)", "8 slots", n(co8["tok_s"]), _ratio(co8["tok_s"] / st8["tok_s"])], ["Continuous batching + CUDA graphs (TinyLlama)", "8 slots", n(gr8["tok_s"]), f"{_ratio(gr8['tok_s'] / co8['tok_s'])} vs eager continuous"], ["Contiguous KV slots (30M model)", f"{c8['kv_reserved_mib']:.0f} MiB of KV, 8 sequences", n(c8["tok_s"]), "—"], ["Paged KV cache (30M model)", f"same {pg['kv_reserved_mib']:.0f} MiB, {pg['peak_active']} sequences", n(pg["tok_s"]), _ratio(pg["tok_s"] / c8["tok_s"])], ]), "", "The paged cache stores KV in fixed-size blocks through a per-sequence block table, as in vLLM, so a " "sequence holds only the blocks it has filled. The share of reserved KV holding real tokens goes from " f"{c8['kv_alloc_used_mean']:.0%} to {pg['kv_alloc_used_mean']:.0%}.", ]) return {"highlights": highlights, "tinyllama": tinyllama, "serving": serving} def replace_block(text: str, name: str, body: str) -> str: """Replace what sits between and ; the rest stays byte-identical.""" start, end = f"", f"" if text.count(start) != 1 or text.count(end) != 1: raise ValueError(f"expected exactly one {start} and one {end}") i, j = text.index(start) + len(start), text.index(end) if j < i: raise ValueError(f"{end} comes before {start}") return text[:i] + "\n" + body.strip("\n") + "\n" + text[j:] def render_readme(text: str, data: dict) -> str: for name, body in readme_blocks(data).items(): text = replace_block(text, name, body) return text def block_names(text: str) -> list[str]: return re.findall(r"", text)