Source files / site · raw
build.py
#!/usr/bin/env python3 """Build the Tersler website: static HTML, no external dependencies. python3 site/build.py [--out site/out] Reads the repo's Markdown docs, the benchmark data in bench/data/ and every source file, and writes a self-contained site (HTML + one CSS file, charts as inline SVG, no JavaScript) plus a tarball of the repository. Python 3.8+, standard library only. """ import argparse, csv, datetime, glob, html, io, json, os, re, shutil, tarfile from collections import defaultdict ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) SITE = os.path.join(ROOT, "site") CSS_VER = __import__("hashlib").sha1(open(os.path.join(SITE, "style.css"), "rb").read()).hexdigest()[:8] # repo path -> (page, nav title); order = navigation order PAGES = [ ("README.md", "index.html", "Overview"), ("docs/recreate.md", "rebuild.html", "Rebuild guide"), (None, "benchmarks.html", "Benchmarks"), ("docs/bom.md", "parts.html", "Parts"), ("docs/hardware-notes.md", "hardware.html", "Hardware notes"), ("image/README.md", "disk-image.html", "Disk image"), ("docs/keeping-up.md", "keeping-up.html", "Keeping up"), ("docs/changelog.md", "changelog.html", "Changelog"), ("docs/research.md", "research.html", "Research log"), ("docs/reference-video-recipe.md", "reference-build.html", "Reference build"), ("TODO.md", "todo.html", "TODO"), (None, "source/index.html", "Source files"), ] PAGE_OF = {src: page for src, page, _ in PAGES if src} PAGE_OF["bench/README.md"] = "benchmarks.html" # not published (vendor PDFs, build output, local state) EXCLUDE_DIRS = {".git", "__pycache__", "image/out", "site/out", "docs/reference", ".claude"} EXCLUDE_LINK = {"docs/reference": "rebuild.html#reference-documents"} TEXT_EXT = {".md", ".sh", ".py", ".conf", ".example", ".service", ".list", ".patch", ".yaml", ".scad", ".txt", ".csv", ".jsonl", ".json", ".css", ""} # ---------------------------------------------------------------- repo files def repo_files(): out = [] for d, dirs, files in os.walk(ROOT): rel = os.path.relpath(d, ROOT).replace(os.sep, "/") rel = "" if rel == "." else rel dirs[:] = sorted(x for x in dirs if (rel + "/" + x).lstrip("/") not in EXCLUDE_DIRS and x not in EXCLUDE_DIRS) for f in sorted(files): if f.endswith(".pyc"): continue out.append((rel + "/" + f).lstrip("/")) return out def is_text(path): _, ext = os.path.splitext(path) if ext not in TEXT_EXT: return False try: open(os.path.join(ROOT, path), encoding="utf-8").read() return True except UnicodeDecodeError: return False # ---------------------------------------------------------------- markdown def slug(text): text = re.sub(r"<[^>]+>", "", text).lower() text = re.sub(r"[^\w\- ]", "", text) return text.replace(" ", "-") class Md: """Markdown subset used by the docs: headings, paragraphs, nested lists and task lists, fenced code, tables, blockquotes, rules, inline code, bold, italics, links, autolinks and images.""" def __init__(self, src_path, page, files): self.src_dir = os.path.dirname(src_path) self.page = page self.files = files self.toc = [] # ---- links def href(self, url): if re.match(r"^[a-z]+:", url) or url.startswith("#"): return url path, _, frag = url.partition("#") target = os.path.normpath(os.path.join(self.src_dir, path)).replace(os.sep, "/").rstrip("/") for k, v in EXCLUDE_LINK.items(): if target == k or target.startswith(k + "/"): return rel_link(self.page, v) if target in PAGE_OF: dest = PAGE_OF[target] elif target in self.files: dest = "source/" + target + ".html" elif any(f.startswith(target + "/") for f in self.files): dest = "source/index.html#" + slug(target.replace("/", "-")) frag = "" else: return url return rel_link(self.page, dest) + ("#" + frag if frag else "") # ---- inline def inline(self, s): codes = [] def keep(m): codes.append("<code>" + html.escape(m.group(2)) + "</code>") return "\x00%d\x00" % (len(codes) - 1) s = re.sub(r"(`+)(.+?)\1", keep, s) s = html.escape(s, quote=False) s = re.sub(r"<(https?://[^\s&]+)>", lambda m: '<a href="%s">%s</a>' % (m.group(1), m.group(1)), s) s = re.sub(r"!\[([^\]]*)\]\(([^)\s]+)\)", lambda m: '<img src="%s" alt="%s">' % (html.escape(self.raw_href(m.group(2))), m.group(1)), s) s = re.sub(r"\[([^\]]+)\]\(([^)\s]+)\)", lambda m: '<a href="%s">%s</a>' % (html.escape(self.href(html.unescape(m.group(2)))), m.group(1)), s) s = re.sub(r"\*\*(.+?)\*\*", r"<strong>\1</strong>", s) s = re.sub(r"(?<![\w*])\*(?![\s*])(.+?)(?<![\s*])\*(?![\w*])", r"<em>\1</em>", s) s = re.sub(r"(?<![\w_])_(?![\s_])(.+?)(?<![\s_])_(?![\w_])", r"<em>\1</em>", s) return re.sub(r"\x00(\d+)\x00", lambda m: codes[int(m.group(1))], s) def raw_href(self, url): if re.match(r"^[a-z]+:", url): return url target = os.path.normpath(os.path.join(self.src_dir, url)).replace(os.sep, "/") return rel_link(self.page, "files/" + target) # ---- blocks LIST_RE = re.compile(r"^( *)([-*+]|\d+[.)]) +(.*)$") FENCE_RE = re.compile(r"^( *)(```+|~~~+)(.*)$") def starts_block(self, line): return bool(re.match(r"^ {0,3}(#{1,6} |```|~~~|> |\|)", line) or self.LIST_RE.match(line) or re.match(r"^ {0,3}(---+|\*\*\*+)\s*$", line)) def blocks(self, lines, tight=False): out, i = [], 0 while i < len(lines): line = lines[i] if not line.strip(): i += 1 continue m = self.FENCE_RE.match(line) if m: ind, fence, lang = len(m.group(1)), m.group(2), m.group(3).strip() i += 1 body = [] while i < len(lines) and not lines[i].strip().startswith(fence): body.append(lines[i][ind:] if lines[i][:ind].strip() == "" else lines[i]) i += 1 i += 1 cls = ' class="lang-%s"' % html.escape(lang) if lang else "" out.append("<pre><code%s>%s</code></pre>" % (cls, html.escape("\n".join(body)))) continue m = re.match(r"^ {0,3}(#{1,6}) +(.*?)#*\s*$", line) if m: lvl, text = len(m.group(1)), self.inline(m.group(2)) sid = slug(text) self.toc.append((lvl, sid, re.sub(r"<[^>]+>", "", text))) out.append('<h%d id="%s"><a class="anchor" href="#%s">#</a>%s</h%d>' % (lvl, sid, sid, text, lvl)) i += 1 continue if re.match(r"^ {0,3}(---+|\*\*\*+)\s*$", line): out.append("<hr>") i += 1 continue if line.lstrip().startswith("|") and i + 1 < len(lines) and re.match(r"^\s*\|[\s:|-]+\|\s*$", lines[i + 1]): rows = [] while i < len(lines) and lines[i].lstrip().startswith("|"): rows.append(lines[i]) i += 1 out.append(self.table(rows)) continue if line.lstrip().startswith(">"): body = [] while i < len(lines) and lines[i].strip() and lines[i].lstrip().startswith(">"): body.append(re.sub(r"^\s*> ?", "", lines[i])) i += 1 out.append("<blockquote>%s</blockquote>" % self.blocks(body)) continue m = self.LIST_RE.match(line) if m: i = self.listblock(lines, i, out) continue para = [line.strip()] i += 1 while i < len(lines) and lines[i].strip() and not self.starts_block(lines[i]): para.append(lines[i].strip()) i += 1 text = self.inline(" ".join(para)) out.append(text if tight else "<p>%s</p>" % text) return "\n".join(out) def listblock(self, lines, i, out): m = m0 = self.LIST_RE.match(lines[i]) base = len(m.group(1)) ordered = m.group(2)[0].isdigit() items = [] while i < len(lines): m = self.LIST_RE.match(lines[i]) if not (m and len(m.group(1)) == base and m.group(2)[0].isdigit() == ordered): break content_ind = len(m.group(1)) + len(m.group(2)) + 1 item = [m.group(3)] i += 1 while i < len(lines): l = lines[i] ind = len(l) - len(l.lstrip()) if not l.strip(): # blank: continues only if the next non-blank line is indented into the item j = i while j < len(lines) and not lines[j].strip(): j += 1 if j < len(lines) and len(lines[j]) - len(lines[j].lstrip()) > base: item.append("") i += 1 continue break if ind <= base and (self.LIST_RE.match(l) or self.starts_block(l)): break item.append(l[min(ind, content_ind):] if ind > base else l) i += 1 items.append(item) n = int(re.match(r"\d*", m0.group(2)).group(0) or 1) start = ' start="%d"' % n if ordered and n != 1 else "" tag = "ol" if ordered else "ul" lis = [] for item in items: tight = "" not in item box = "" mm = re.match(r"^\[([ xX])\] (.*)$", item[0]) if mm: box = '<span class="box%s">%s</span> ' % (" done" if mm.group(1) != " " else "", "✓" if mm.group(1) != " " else "") item = [mm.group(2)] + item[1:] lis.append("<li%s>%s%s</li>" % (' class="task"' if box else "", box, self.blocks(item, tight))) out.append("<%s%s>\n%s\n</%s>" % (tag, start, "\n".join(lis), tag)) return i def table(self, rows): def cells(row): row = row.strip().strip("|") parts, cur, code = [], "", False for ch in row: if ch == "`": code = not code if ch == "|" and not code: parts.append(cur) cur = "" else: cur += ch parts.append(cur) return [p.strip() for p in parts] head, aligns = cells(rows[0]), cells(rows[1]) al = [] for a in aligns: al.append(' class="r"' if a.endswith(":") and not a.startswith(":") else "") h = "".join("<th>%s</th>" % self.inline(c) for c in head) body = [] for r in rows[2:]: cs = cells(r) body.append("<tr>%s</tr>" % "".join("<td%s>%s</td>" % (al[k] if k < len(al) else "", self.inline(c)) for k, c in enumerate(cs))) return '<div class="tw"><table><thead><tr>%s</tr></thead><tbody>\n%s\n</tbody></table></div>' % (h, "\n".join(body)) def render(self, text): return self.blocks(text.replace("\t", " ").split("\n")) def rel_link(from_page, to_page): up = from_page.count("/") return "../" * up + to_page # ---------------------------------------------------------------- page shell def shell(page, title, body, toc=None, desc=""): nav = [] for _, p, t in PAGES: cur = ' aria-current="page"' if p == page else "" nav.append('<a href="%s"%s>%s</a>' % (rel_link(page, p), cur, t)) nav.append('<a href="%s">Download (.tar.gz)</a>' % rel_link(page, "tersler.tar.gz")) side = "" if toc: items = "".join('<a class="l%d" href="#%s">%s</a>' % (l, s, html.escape(t)) for l, s, t in toc if l in (2, 3)) if items: side = '<nav class="toc"><div class="toch">On this page</div>%s</nav>' % items return """<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>%s · Tersler</title> <meta name="description" content="%s"> <link rel="stylesheet" href="%s?v=%s"> </head> <body> <header class="top"><a class="brand" href="%s">Tersler</a><span class="tag">The little junk rig that could</span></header> <div class="wrap"> <nav class="side">%s</nav> <main> %s </main> %s </div> <footer>Built %s from the Tersler repository · <a href="%s">source</a> · no cookies, no scripts, no external requests</footer> </body> </html> """ % (html.escape(title), html.escape(desc), rel_link(page, "style.css"), CSS_VER, rel_link(page, "index.html"), "\n".join(nav), body, side, datetime.date.today().isoformat(), rel_link(page, "source/index.html")) # ---------------------------------------------------------------- charts (inline SVG) def bar_chart(groups, series, values, ymax=100, unit="%", height=260, label_fmt="{:.0f}"): """groups: x labels; series: legend names; values[s][g].""" W, H, L, B, T = 720, height, 40, 34, 14 gw = (W - L - 10) / len(groups) bw = min(26, (gw - 14) / len(series)) p = ['<svg class="chart" viewBox="0 0 %d %d" role="img">' % (W, H)] for k in range(0, 5): v = ymax * k / 4 y = H - B - (H - B - T) * k / 4 p.append('<line class="grid" x1="%d" x2="%d" y1="%.1f" y2="%.1f"/>' % (L, W - 10, y, y)) p.append('<text class="ax" x="%d" y="%.1f" text-anchor="end">%s</text>' % (L - 6, y + 4, label_fmt.format(v))) for g, name in enumerate(groups): x0 = L + g * gw + (gw - bw * len(series)) / 2 for s in range(len(series)): v = values[s][g] if v is None: continue h = (H - B - T) * min(v, ymax) / ymax x = x0 + s * bw p.append('<rect class="s%d" x="%.1f" y="%.1f" width="%.1f" height="%.1f" rx="2"><title>%s, %s: %s%s</title></rect>' % (s, x + 1, H - B - h, bw - 2, h, html.escape(series[s]), html.escape(name), label_fmt.format(v), unit)) p.append('<text class="val" x="%.1f" y="%.1f" text-anchor="middle">%s</text>' % (x + bw / 2, H - B - h - 4, label_fmt.format(v))) p.append('<text class="ax" x="%.1f" y="%d" text-anchor="middle">%s</text>' % (L + g * gw + gw / 2, H - B + 20, html.escape(name))) p.append("</svg>") leg = "".join('<span><i class="s%d"></i>%s</span>' % (s, html.escape(n)) for s, n in enumerate(series)) return '<figure>%s<figcaption class="legend">%s</figcaption></figure>' % ("".join(p), leg) def line_chart(series, ymin, ymax, xmax, hlines=(), height=220, xunit="min"): W, H, L, B, T = 720, height, 40, 30, 10 sx = lambda x: L + (W - L - 10) * x / xmax sy = lambda y: H - B - (H - B - T) * (y - ymin) / (ymax - ymin) p = ['<svg class="chart" viewBox="0 0 %d %d" role="img">' % (W, H)] for k in range(5): v = ymin + (ymax - ymin) * k / 4 p.append('<line class="grid" x1="%d" x2="%d" y1="%.1f" y2="%.1f"/>' % (L, W - 10, sy(v), sy(v))) p.append('<text class="ax" x="%d" y="%.1f" text-anchor="end">%.0f</text>' % (L - 6, sy(v) + 4, v)) for k in range(6): x = xmax * k / 5 p.append('<text class="ax" x="%.1f" y="%d" text-anchor="middle">%.0f %s</text>' % (sx(x), H - 8, x, xunit)) for v, name in hlines: p.append('<line class="limit" x1="%d" x2="%d" y1="%.1f" y2="%.1f"/><text class="ax lim" x="%d" y="%.1f">%s</text>' % (L, W - 10, sy(v), sy(v), L + 4, sy(v) - 4, html.escape(name))) for s, (name, pts) in enumerate(series): d = " ".join("%.1f,%.1f" % (sx(x), sy(y)) for x, y in pts) p.append('<polyline class="ln s%d" points="%s"><title>%s</title></polyline>' % (s, d, html.escape(name))) p.append("</svg>") leg = "".join('<span><i class="s%d"></i>%s</span>' % (s, html.escape(n)) for s, (n, _) in enumerate(series)) return '<figure>%s<figcaption class="legend">%s</figcaption></figure>' % ("".join(p), leg) STEP_NAMES = {"root_turn1": "root, turn 1", "root_turn2": "root, turn 2", "sub0": "subagent 1", "sub1": "subagent 2", "sub2": "subagent 3", "a_cold": "a) cold", "b_append_turn": "b) turn appended", "c_edit_middle": "c) middle edited", "d_other_slot_same_prefix": "d) other slot, same prefix"} def num(v): try: return float(v) except ValueError: return 0.0 def table(head, rows, right=()): h = "".join("<th%s>%s</th>" % (' class="r"' if k in right else "", c) for k, c in enumerate(head)) b = "\n".join("<tr>%s</tr>" % "".join("<td%s>%s</td>" % (' class="r"' if k in right else "", c) for k, c in enumerate(r)) for r in rows) return '<div class="tw"><table><thead><tr>%s</tr></thead><tbody>%s</tbody></table></div>' % (h, b) # ---------------------------------------------------------------- benchmark data def lean_bench(notes): runs = [] for fn, (model, setup) in notes.items(): path = os.path.join(ROOT, "bench/data/lean_bench", fn) if not os.path.exists(path): continue items = [json.loads(l) for l in open(path) if l.strip()] by = defaultdict(list) for r in items: by[(r["category"], r["level"])].append(r["correct"]) cats = {} for cat in ("math", "logic", "code"): lv = [sum(by[(cat, l)]) / len(by[(cat, l)]) for l in (1, 2, 3) if by[(cat, l)]] n = [r for r in items if r["category"] == cat] cats[cat] = {"acc": sum(r["correct"] for r in n) / len(n), "bal": sum(lv) / len(lv), "n": len(n)} fam = defaultdict(list) for r in items: fam[(r["category"], r["family"])].append(r) runs.append({"file": fn, "model": model, "setup": setup, "items": items, "cats": cats, "fam": fam, "overall": sum(c["bal"] for c in cats.values()) / 3, "trunc": sum(1 for r in items if r["finish_reason"] == "length"), "hours": sum(r["seconds"] or 0 for r in items) / 3600, "tok": sum(r["completion_tokens"] or 0 for r in items), "cap": "32k" if "32k" in fn else "16k"}) return runs def benchmarks_page(notes, md_intro): page = "benchmarks.html" toc = [(2, "lean_bench", "lean_bench (quality)"), (2, "decode", "Decode speed (tersler-bench)"), (2, "speed", "Prefill and decode (speed test)"), (2, "multi-session", "Multi-session tests"), (2, "tools", "Tool calling"), (2, "thermals", "Thermals (load tests)"), (2, "raw-data", "Raw data")] out = ['<h1>Benchmarks</h1>', '<p class="lead">All numbers come from the files in <a href="%s">bench/data/</a> and are recomputed on every site build.</p>' % rel_link(page, "source/index.html#bench-data")] # ---- lean_bench runs = lean_bench(notes["lean_bench"]) runs32 = [r for r in runs if r["cap"] == "32k"] out.append('<h2 id="lean_bench"><a class="anchor" href="#lean_bench">#</a>lean_bench (quality)</h2>') out.append("<p>158 generated items (72 math, 42 logic, 44 code) at three difficulty levels, seed 1, " "temperature 1.0, top_p 0.95, top_k 20, thinking on, one request at a time. " "The overall score is the mean over the three categories of the level-balanced accuracy. " "One seed at temperature 1.0 means roughly ±10–15 points per category.</p>") names = ["%s (%s)" % (r["model"], r["setup"].split(",")[0]) for r in runs32] vals = [[r["cats"][c]["acc"] * 100 for c in ("math", "logic", "code")] + [r["overall"] * 100] for r in runs32] out.append(bar_chart(["Math", "Logic", "Code", "Overall"], names, vals)) rows = [] for r in sorted(runs, key=lambda r: -r["overall"]): rows.append(["<strong>%s</strong><br><small>%s</small>" % (html.escape(r["model"]), html.escape(r["setup"])), "%.1f%%" % (r["cats"]["math"]["acc"] * 100), "%.1f%%" % (r["cats"]["logic"]["acc"] * 100), "%.1f%%" % (r["cats"]["code"]["acc"] * 100), str(r["trunc"]), "%.1f h" % r["hours"], "<strong>%.3f</strong>" % r["overall"]]) out.append(table(["Model and setup", "Math", "Logic", "Code", "Cut off", "Time", "Overall"], rows, right=(1, 2, 3, 4, 5, 6))) out.append("<p><em>Cut off</em> = the answer hit the token cap before it finished; it counts as wrong. " "Time is wall time on one machine, so it is not comparable between Tersler and moppel.</p>") # per family fams = sorted({k for r in runs32 for k in r["fam"]}) rows = [] for cat, fam in fams: row = ["%s/%s" % (cat, fam)] for r in runs32: it = r["fam"].get((cat, fam), []) ok = sum(x["correct"] for x in it) cut = sum(1 for x in it if x["finish_reason"] == "length") cls = "good" if ok == len(it) else ("bad" if ok * 2 < len(it) else "") row.append('<span class="%s">%d/%d</span>%s' % (cls, ok, len(it), " <small>(%d cut)</small>" % cut if cut else "")) rows.append(row) out.append("<details><summary>Per task family (32k cap)</summary>%s</details>" % table(["Family"] + [html.escape(r["model"]) for r in runs32], rows, right=tuple(range(1, len(runs32) + 1)))) # ---- decode out.append('<h2 id="decode"><a class="anchor" href="#decode">#</a>Decode speed (tersler-bench)</h2>') out.append(md_intro) rows = list(csv.DictReader(open(os.path.join(ROOT, "bench/data/results.csv")))) groups = [] # one bench run = consecutive rows, one per prompt, same build and power for r in rows: g = groups[-1] if groups else None if not g or r["prompt"] in g or any(x["commit"] != r["commit"] or x["power_limit_w"] != r["power_limit_w"] for x in g.values()): groups.append({}) groups[-1][r["prompt"]] = r trs = [] for pr in groups: r0 = min(pr.values(), key=lambda x: x["date"]) commit, pw = r0["commit"], r0["power_limit_w"] note = next((v for k, v in notes["results"].items() if r0["date"].startswith(k)), "") def cell(p, f, fmt="%.1f"): return fmt % float(pr[p][f]) if p in pr else "—" acc = [float(pr[p]["draft_accept"]) for p in pr if pr[p]["draft_accept"]] first = min(x["date"] for x in pr.values()) trs.append(["<span class=\"nw\">%s</span>" % first[5:16].replace("T", " "), "<code>%s</code>" % commit, "%.0f W" % float(pw), cell("puzzle", "tg_tok_s"), cell("document", "tg_tok_s"), cell("code", "tg_tok_s"), cell("document", "pp_tok_s", "%.0f"), "%.2f" % (sum(acc) / len(acc)) if acc else "—", "<small>%s</small>" % html.escape(note)]) out.append(table(["Run (2026)", "Build", "Power", "tg puzzle", "tg doc", "tg code", "pp doc", "Draft accept", "Note"], trs, right=(3, 4, 5, 6, 7))) out.append("<p>tg = decode tok/s, pp = prompt processing tok/s (~5k-token document). Model: Qwen3.8 27B UD-Q6_K " "with MTP speculative decoding, both cards with <code>-sm tensor</code>. Medians of 3 runs, 512 tokens, thinking on.</p>") # ---- speed test path = os.path.join(ROOT, "bench/data/speed.jsonl") if os.path.exists(path): recs = [json.loads(l) for l in open(path) if l.strip()] sn = notes.get("speed", {}) out.append('<h2 id="speed"><a class="anchor" href="#speed">#</a>Prefill and decode (speed test)</h2>') out.append('<p>From <a href="%s">scripts/speed-test.py</a>: cold prompts (nothing cached), thinking off, up to 512 tokens, ' 'timed on the client. Prefill = prompt tokens / time to first token. Two runs per size.</p>' % rel_link(page, "source/scripts/speed-test.py.html")) trs = [] for lab in dict.fromkeys(r["label"] for r in recs): for size in dict.fromkeys(r["size"] for r in recs if r["label"] == lab): rs = [r for r in recs if r["label"] == lab and r["size"] == size] pt = max(r["prompt_tokens"] for r in rs) trs.append([html.escape(sn.get(lab, lab)), "{:,}".format(pt), " / ".join("%.0f" % r["pp_tok_s"] for r in rs) if size else "—", " / ".join("%.1f" % r["tg_tok_s"] for r in rs if r["tg_tok_s"])]) out.append(table(["Engine and setup", "Prompt tokens", "Prefill tok/s", "Decode tok/s"], trs, right=(1, 2, 3))) # ---- multi-session out.append('<h2 id="multi-session"><a class="anchor" href="#multi-session">#</a>Multi-session tests</h2>') out.append('<p>From <a href="%s">scripts/concurrency-test.py</a>; background in the ' '<a href="research.html#multi-session-test-2026-11-07">research log</a>.</p>' % rel_link(page, "source/scripts/concurrency-test.py.html")) recs = [json.loads(l) for l in open(os.path.join(ROOT, "bench/data/concurrency.jsonl")) if l.strip()] cn = notes["concurrency"] conc = [r for r in recs if r["test"] == "concurrency"] trs = [[html.escape(cn.get(r["label"], r["label"])), str(r["n"]), "%.1f" % r["total_tok_s"], " · ".join("%.1f" % x for x in r["per_req_tg"]), "%.2f" % r["accept"] if r.get("accept") else "—"] for r in conc] out.append("<h3>Parallel requests</h3>") out.append(table(["Server config", "Parallel", "Total tok/s", "Per request tok/s", "Draft accept"], trs, right=(1, 2, 3, 4))) for test, title, desc in (("swarm", "Swarm: root agent + 3 subagents sharing a 29k-token system prompt", "prompt_n = tokens the server had to process; cached = tokens reused from the cache."), ("replay", "Replay: prompt reuse in an agent loop", "a) cold ~26k-token prompt, b) one more turn appended, c) a tool output edited in the middle, " "d) the same system prompt in another slot (a subagent).")): labels = [] for r in recs: if r["test"] == test and r["label"] not in labels: labels.append(r["label"]) steps = [] for r in recs: if r["test"] == test and r["step"] not in steps: steps.append(r["step"]) trs = [] for lab in labels: row = [html.escape(cn.get(lab, lab))] for s in steps: r = next((x for x in recs if x["test"] == test and x["label"] == lab and x["step"] == s), None) row.append("%s s<br><small>%s new<br>%s cached</small>" % (r["wall"], "{:,}".format(r["prompt_n"]), "{:,}".format(r["cache_n"])) if r else "—") trs.append(row) out.append("<h3>%s</h3><p>%s</p>" % (html.escape(title), desc)) out.append(table(["Server config"] + [html.escape(STEP_NAMES.get(x, x)) for x in steps], trs, right=tuple(range(1, len(steps) + 1)))) # ---- tool calling path = os.path.join(ROOT, "bench/data/tools.jsonl") if os.path.exists(path): recs = [json.loads(l) for l in open(path) if l.strip()] out.append('<h2 id="tools"><a class="anchor" href="#tools">#</a>Tool calling</h2>') out.append('<p>From <a href="%s">scripts/tool-test.py</a>: short conversations with tool definitions, thinking on, ' 'each case three times. The money case is a rent total with late fees (expected 801.69 EUR), with a ' 'calculator tool offered.</p>' % rel_link(page, "source/scripts/tool-test.py.html")) labs = list(dict.fromkeys(r["label"] for r in recs)) cases = list(dict.fromkeys(r["case"] for r in recs)) trs = [] for c in cases: row = [c.replace("_", " ")] for lab in labs: rs = [r for r in recs if r["label"] == lab and r["case"] == c] ok = sum(r["ok"] for r in rs) row.append('<span class="%s">%d/%d</span>' % ("good" if ok == len(rs) else "bad", ok, len(rs)) if rs else "—") trs.append(row) out.append(table(["Case"] + [html.escape(notes.get("tools", {}).get(l, l)) for l in labs], trs, right=tuple(range(1, len(labs) + 1)))) # ---- thermals out.append('<h2 id="thermals"><a class="anchor" href="#thermals">#</a>Thermals (load tests)</h2>') out.append("<p>From <code>tersler-loadtest</code>: sustained decode with 4096-token answers. The P100 slows down at " "82 °C and shuts down at 85 °C. The fan follows the hotter card.</p>") trs = [] for f in sorted(glob.glob(os.path.join(ROOT, "bench/data/loadtest-*.csv"))): data = list(csv.DictReader(open(f))) g = defaultdict(list) for r in data: g[r["gpu"]].append((int(r["time_s"]) / 60, float(r["temp_c"]), float(r["power_w"]), num(r["pwm"]), num(r["fan_rpm"]))) xmax = max(p[0] for v in g.values() for p in v) or 1 name = os.path.basename(f)[9:-4] when = "%s-%s-%s %s:%s" % (name[:4], name[4:6], name[6:8], name[9:11], name[11:13]) out.append("<h3>%s</h3>" % when) out.append(line_chart([("GPU %s °C" % k, [(p[0], p[1]) for p in v]) for k, v in sorted(g.items())], 30, 90, xmax, hlines=[(82, "82 °C slowdown")])) trs.append([when, "%.0f min" % xmax] + ["%.0f / %.0f °C" % tuple(max(p[1] for p in g[k]) for k in sorted(g))] + ["%.0f / %.0f W" % tuple(sum(p[2] for p in g[k]) / len(g[k]) for k in sorted(g))] + ["%d rpm" % max(p[4] for v in g.values() for p in v) if any(p[4] for v in g.values() for p in v) else "—"]) out.append(table(["Run", "Length", "Max temp (GPU 0 / 1)", "Avg power", "Max fan"], trs, right=(1, 2, 3, 4))) # ---- raw out.append('<h2 id="raw-data"><a class="anchor" href="#raw-data">#</a>Raw data</h2><ul>') for f in sorted(glob.glob(os.path.join(ROOT, "bench/data/**/*.*"), recursive=True)): rel = os.path.relpath(f, ROOT).replace(os.sep, "/") out.append('<li><a href="files/%s">%s</a></li>' % (rel, rel)) out.append("</ul>") return shell(page, "Benchmarks", "\n".join(out), toc, "Tersler benchmark results"), runs # ---------------------------------------------------------------- main def main(): ap = argparse.ArgumentParser() ap.add_argument("--out", default=os.path.join(SITE, "out")) out = ap.parse_args().out if os.path.isdir(out): shutil.rmtree(out) os.makedirs(out) files = [f for f in repo_files() if not f.startswith("site/out/")] notes = json.load(open(os.path.join(ROOT, "bench/data/notes.json"))) def write(page, text): p = os.path.join(out, page) os.makedirs(os.path.dirname(p), exist_ok=True) with open(p, "w", encoding="utf-8") as f: f.write(text) # raw files for f in files: dst = os.path.join(out, "files", f) os.makedirs(os.path.dirname(dst), exist_ok=True) shutil.copy2(os.path.join(ROOT, f), dst) shutil.copy2(os.path.join(SITE, "style.css"), os.path.join(out, "style.css")) shutil.copytree(os.path.join(SITE, "img"), os.path.join(out, "img")) # benchmarks md = Md("bench/README.md", "benchmarks.html", files) intro = md.render(re.sub(r"^# .*\n", "", open(os.path.join(ROOT, "bench/README.md")).read())) bench_html, runs = benchmarks_page(notes, intro) write("benchmarks.html", bench_html) # doc pages for src, page, title in PAGES: if not src: continue md = Md(src, page, files) body = md.render(open(os.path.join(ROOT, src), encoding="utf-8").read()) if page == "index.html": body = overview(runs) + body if page == "hardware.html": imgs = [f for f in files if f.startswith("shroud/") and f.endswith(".png")] body += '<h2 id="duct-renders">Duct renders</h2><div class="gallery">%s</div>' % "".join( '<a href="files/%s"><img src="files/%s" alt="%s" loading="lazy"></a>' % (f, f, os.path.basename(f)) for f in imgs) md.toc.append((2, "duct-renders", "Duct renders")) write(page, shell(page, title, body, md.toc if page != "index.html" else None, title + " · Tersler")) # source browser tree = defaultdict(list) for f in files: tree[os.path.dirname(f)].append(f) parts = ['<h1>Source files</h1><p class="lead">Everything in the repository except build output. ' 'Download it all as <a href="../tersler.tar.gz">tersler.tar.gz</a>. ' 'Start with the <a href="../rebuild.html">rebuild guide</a>.</p>'] toc = [] for d in sorted(tree): name = d or "(top level)" sid = slug(d.replace("/", "-")) if d else "top" toc.append((2, sid, name)) rows = [] for f in tree[d]: size = os.path.getsize(os.path.join(ROOT, f)) if is_text(f): link = '<a href="%s.html">%s</a>' % (f, os.path.basename(f)) else: link = '<a href="../files/%s">%s</a>' % (f, os.path.basename(f)) rows.append([link, "%.1f kB" % (size / 1024)]) parts.append('<h2 id="%s"><a class="anchor" href="#%s">#</a>%s/</h2>' % (sid, sid, html.escape(name))) parts.append(table(["File", "Size"], rows, right=(1,))) write("source/index.html", shell("source/index.html", "Source files", "\n".join(parts), toc)) for f in files: if not is_text(f): continue page = "source/" + f + ".html" text = open(os.path.join(ROOT, f), encoding="utf-8").read() lines = text.split("\n") if lines and lines[-1] == "": lines.pop() body = '<p class="crumb"><a href="%s">Source files</a> / %s · <a href="%s">raw</a>%s</p><h1>%s</h1>' % ( rel_link(page, "source/index.html"), html.escape(os.path.dirname(f) or "."), rel_link(page, "files/" + f), ' · <a href="%s">rendered</a>' % rel_link(page, PAGE_OF[f]) if f in PAGE_OF else "", html.escape(os.path.basename(f))) body += '<pre class="src">%s</pre>' % "".join('<span class="ln">%s\n</span>' % html.escape(l) for l in lines) write(page, shell(page, f, body)) # tarball of the repo (same file set) with tarfile.open(os.path.join(out, "tersler.tar.gz"), "w:gz") as tar: for f in files: tar.add(os.path.join(ROOT, f), arcname="tersler/" + f, recursive=False) n = sum(1 for _ in glob.glob(os.path.join(out, "**/*.html"), recursive=True)) print("site: %s (%d pages, %d files)" % (out, n, len(files))) def overview(runs): # the serving setup: Flash-Next on Strata (Tersler run only; the same model also ran on another box) best = {} for r in sorted(runs, key=lambda r: r["overall"]): best[(r["model"], r["setup"].startswith("Tersler"))] = r speed = [json.loads(l) for l in open(os.path.join(ROOT, "bench/data/speed.jsonl")) if l.strip()] speed = [r for r in speed if r["label"].startswith("strata")] short = [r["tg_tok_s"] for r in speed if r["size"] == 0] deep = [r for r in speed if r["size"] == max(x["size"] for x in speed)] total = re.search(r"\*\*Total\*\*.*?€([\d.]+)", open(os.path.join(ROOT, "docs/bom.md")).read()) cards = [("€%.0f" % float(total.group(1)) if total else "—", "all parts, incl. 2 P100 cards and 64 GB RAM")] if short: cards.append(("%.0f–%.0f tok/s" % (min(short), max(short)), "Qwen3.8 Flash-Next IQ3_S decode on Strata, MTP")) if deep: n = sum(r["prompt_tokens"] for r in deep) / len(deep) cards.append(("%.0f tok/s" % (sum(r["pp_tok_s"] for r in deep) / len(deep)), "prefill of a %.0fk-token prompt; decode there %.0f tok/s" % (n / 1000, sum(r["tg_tok_s"] for r in deep) / len(deep)))) cards += [("262k", "context, int8 KV; recall held at 204k tokens"), ("Text + images", "vision encoder on the CPU"), ("≤ 75 °C", "under sustained load, 150 W per card")] for (m, eng) in (("Qwen3.8 Flash-Next IQ3_S", "Strata"), ("Qwen3.8 27B Q6_K", "llama.cpp, the alternative")): r = best.get((m, True)) if r: cards.append(("%.3f" % r["overall"], "lean_bench, %s (%s)" % (m.replace("Qwen3.8 ", ""), eng))) return '<div class="hero"><figure><img src="img/ermahgerd-p100.png" alt="Ermahgerd meme: a girl holding two Tesla P100 cards, captioned ERMAHGERD, TERSLER P-HERNDRERDS!" width="804" height="1208"></figure><div class="cards">%s</div></div><p class="lead"><a href="rebuild.html">Rebuild guide →</a> · <a href="benchmarks.html">All benchmarks →</a></p>' % "".join( '<div class="card"><b>%s</b><span>%s</span></div>' % (html.escape(a), html.escape(b)) for a, b in cards) if __name__ == "__main__": main()