SPB Git

spb/localvm-research Public License

Running LLMs larger than memory on a consumer Mac — falsification-driven research: margin-gated deferred refinement, out-of-core verification on Apple Silicon. TR-01 published.

Python 63.2% JavaScript 23.5% CSS 11.8% Shell 0.9% Makefile 0.5%
11.0 KB · 255 lines javascript
Raw Blame History
1// ============================================================================2//  Project   : localvm-research3//  File      : web/lib/render.js4//  Purpose   : HTML layout, markdown/code rendering, SVG chart for the platform5//  Author    : Simon-Pierre Boucher6//  Contact   : contact@spboucher.ai7//  Created   : 2026-08-128//  Modified  : 2026-08-129//  Platform  : macOS / Apple Silicon (arm64) — Node.js10//  License   : All rights reserved (research code)11// ============================================================================12"use strict";1314const { Marked } = require("marked");15const hljs = require("highlight.js");1617function esc(s) {18  return String(s).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c]));19}2021function slugify(s) {22  return s.toLowerCase().replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-").slice(0, 80);23}2425/**26 * Render markdown → { html, toc }. A fresh Marked instance per call (a shared27 * global `marked.use()` would stack walkTokens handlers on every render).28 * Headings get stable anchor ids collected into a table of contents.29 */30function markdownToHtml(md, baseRel) {31  const toc = [];32  const seen = new Set();33  const m = new Marked({34    renderer: {35      code({ text, lang }) {36        const html = lang && hljs.getLanguage(lang)37          ? hljs.highlight(text, { language: lang }).value38          : esc(text);39        return `<pre class="codeblock"><code class="hljs">${html}</code></pre>`;40      },41      heading({ tokens, depth }) {42        const html = this.parser.parseInline(tokens);43        const plain = html.replace(/<[^>]+>/g, "");44        let id = slugify(plain) || "section";45        while (seen.has(id)) id += "-x";46        seen.add(id);47        if (depth >= 2 && depth <= 3) toc.push({ depth, id, text: plain });48        return `<h${depth} id="${id}">${html}<a class="hanchor" href="#${id}" aria-label="Link to section">#</a></h${depth}>\n`;49      },50    },51    walkTokens(token) {52      if (token.type === "image" && token.href && !/^(https?:|data:|\/)/.test(token.href)) {53        const base = baseRel.includes("/") ? baseRel.slice(0, baseRel.lastIndexOf("/")) : "";54        token.href = "/raw/" + (base ? base + "/" : "") + token.href;55        return;56      }57      if (token.type === "link" && token.href && !/^(https?:|mailto:|#|\/)/.test(token.href)) {58        const base = baseRel.includes("/") ? baseRel.slice(0, baseRel.lastIndexOf("/")) : "";59        const joined = (base ? base + "/" : "") + token.href;60        const norm = joined.split("/").reduce((acc, part) => {61          if (part === "..") acc.pop();62          else if (part !== "." && part !== "") acc.push(part);63          return acc;64        }, []).join("/");65        token.href = norm.endsWith(".md") ? "/doc/" + norm : "/file/" + norm;66      }67    },68  });69  return { html: m.parse(md), toc };70}7172/** Sticky table-of-contents sidebar for long documents. */73function tocHtml(toc) {74  if (!toc || toc.length < 3) return "";75  const items = toc76    .map((t) => `<li class="toc-d${t.depth}"><a href="#${t.id}">${esc(t.text)}</a></li>`)77    .join("");78  return `<nav class="toc" aria-label="Table of contents"><span class="toc-title">On this page</span><ul>${items}</ul></nav>`;79}8081/** Professional document header from front matter. */82function docHeader(data, rel) {83  const title = (data.document || rel).split("/").pop().replace(/[_-]/g, " ");84  const status = data.status || "draft";85  const meta = [86    data.created ? `created ${data.created}` : null,87    data.modified ? `updated ${data.modified}` : null,88    data.author || null,89  ].filter(Boolean).join(" · ");90  return `<header class="doc-head">91    <div class="doc-head-row">92      <h1>${esc(title)}</h1>93      <span class="badge badge-${status === "reviewed" || status === "final" ? "done" : "in-progress"}">${esc(status)}</span>94    </div>95    <p class="doc-head-meta">${esc(data.document || rel)}${meta ? " · " + esc(meta) : ""}</p>96  </header>`;97}9899/** Research-log timeline: entries already split by content.parseLogEntries. */100function logTimeline(entries, baseRel) {101  const items = entries102    .slice()103    .reverse()104    .map((e) => {105      const [when, ...rest] = e.title.split(" — ");106      const title = rest.join(" — ") || when;107      const body = markdownToHtml(e.body, baseRel).html;108      return `<article class="tl-entry">109        <div class="tl-rail"><span class="tl-dot"></span></div>110        <div class="tl-card">111          <time class="tl-time">${esc(rest.length ? when : "")}</time>112          <h3>${esc(title)}</h3>113          <div class="md md-compact">${body}</div>114        </div>115      </article>`;116    })117    .join("");118  return `<div class="timeline">${items}</div>`;119}120121function highlightFile(text, filename) {122  const ext = filename.slice(filename.lastIndexOf(".") + 1).toLowerCase();123  const langMap = {124    py: "python", sh: "bash", js: "javascript", css: "css", json: "json",125    toml: "ini", yaml: "yaml", yml: "yaml", cff: "yaml", md: "markdown",126    metal: "cpp", cpp: "cpp", h: "cpp", hpp: "cpp", swift: "swift", m: "objectivec",127  };128  const lang = filename === "Makefile" ? "makefile" : langMap[ext];129  if (lang && hljs.getLanguage(lang)) return hljs.highlight(text, { language: lang }).value;130  return esc(text);131}132133const NAV = [134  ["/", "Home"],135  ["/research", "Research"],136  ["/experiments", "Experiments"],137  ["/results", "Results"],138  ["/code", "Code"],139  ["/doc/research/bibliography.md", "Bibliography"],140  ["/publications", "Publications"],141  ["/comments", "Comments"],142  ["/about", "About"],143];144145function layout({ title, active, body, buildInfo = {} }) {146  const nav = NAV.map(147    ([href, label]) =>148      `<a href="${href}" class="${active === label ? "active" : ""}">${label}</a>`149  ).join("");150  const commit = buildInfo.commit ? buildInfo.commit.slice(0, 7) : "dev";151  const synced = buildInfo.generated ? buildInfo.generated.slice(0, 10) : "";152  return `<!DOCTYPE html>153<html lang="en">154<head>155<meta charset="utf-8">156<meta name="viewport" content="width=device-width, initial-scale=1">157<title>${esc(title)} · localvm-research</title>158<meta name="description" content="localvm-research — running LLMs larger than memory on consumer Apple Silicon. Research by Simon-Pierre Boucher.">159<link rel="stylesheet" href="/static/style.css?v=${esc(commit)}">160<link rel="icon" type="image/svg+xml" href="/static/logo.svg">161<link rel="apple-touch-icon" href="/static/logo.svg">162</head>163<body>164<header class="site-header">165  <div class="wrap header-row">166    <a class="brand" href="/"><img class="brand-logo" src="/static/logo.svg" alt="" width="26" height="26">localvm<span class="brand-dim">-research</span></a>167    <button class="nav-toggle" id="nav-toggle" aria-label="Menu" aria-expanded="false">168      <span></span><span></span><span></span>169    </button>170    <nav class="site-nav" id="site-nav">${nav}</nav>171  </div>172</header>173<main class="wrap">${body}</main>174<footer class="site-footer">175  <div class="wrap footer-grid">176    <div>177      <span class="footer-brand">localvm-research</span>178      <p class="footer-tag">Running LLMs larger than memory on consumer Apple Silicon —179      an open, falsification-driven research trail.</p>180    </div>181    <div class="footer-col">182      <span class="footer-h">Project</span>183      <a href="/doc/CLAUDE.md">Charter</a>184      <a href="/doc/research/LOG.md">Research log</a>185      <a href="/experiments">Experiments</a>186    </div>187    <div class="footer-col">188      <span class="footer-h">Author</span>189      <span>Simon-Pierre Boucher</span>190      <a href="mailto:contact@spboucher.ai">contact@spboucher.ai</a>191      <span class="footer-meta">snapshot ${esc(commit)}${synced ? " · " + esc(synced) : ""}</span>192    </div>193  </div>194  <div class="wrap footer-legal">© 2026 Simon-Pierre Boucher · All rights reserved (research code) · Apple M5 Max / 48 GB / macOS 27</div>195</footer>196<script src="/static/app.js?v=${esc(commit)}" defer></script>197</body>198</html>`;199}200201/**202 * SSD throughput line chart (log-y), server-rendered SVG.203 * Form: change-of-magnitude across an ordered log2 x (block size), 3 series (QD).204 * Palette: validated categorical slots 1–3 (see dataviz reference palette);205 * relief for the aqua WARN is provided by direct labels at line ends.206 */207function ssdChartSvg(chart) {208  if (!chart) return "";209  const SERIES_COLORS = { 1: "#2a78d6", 4: "#eb6834", 8: "#1baf7a" };210  const W = 720, H = 340, ML = 64, MR = 96, MT = 18, MB = 44;211  const iw = W - ML - MR, ih = H - MT - MB;212  const blocks = [4096, 16384, 65536, 262144, 1048576];213  const xPos = (b) => ML + (Math.log2(b) - 12) / (20 - 12) * iw;214  const yMin = Math.log10(50), yMax = Math.log10(20000);215  const yPos = (v) => MT + ih - ((Math.log10(v) - yMin) / (yMax - yMin)) * ih;216217  const gridVals = [100, 1000, 10000];218  let g = "";219  for (const v of gridVals) {220    g += `<line x1="${ML}" y1="${yPos(v)}" x2="${ML + iw}" y2="${yPos(v)}" class="grid"/>` +221      `<text x="${ML - 8}" y="${yPos(v) + 4}" class="tick" text-anchor="end">${v >= 1000 ? v / 1000 + " GB/s" : v + " MB/s"}</text>`;222  }223  let xticks = "";224  for (const b of blocks) {225    const lbl = b >= 1048576 ? "1 MiB" : b >= 1024 ? b / 1024 + " KiB" : b + " B";226    xticks += `<text x="${xPos(b)}" y="${MT + ih + 20}" class="tick" text-anchor="middle">${lbl}</text>`;227  }228  let lines = "", dots = "", labels = "";229  const qds = Object.keys(chart.series).map(Number).sort((a, b) => a - b);230  for (const qd of qds) {231    const pts = chart.series[qd].filter((p) => blocks.includes(p.block));232    if (!pts.length) continue;233    const c = SERIES_COLORS[qd] || "#2a78d6";234    const d = pts.map((p, i) => `${i ? "L" : "M"}${xPos(p.block).toFixed(1)},${yPos(p.mbps).toFixed(1)}`).join(" ");235    lines += `<path d="${d}" fill="none" stroke="${c}" stroke-width="2" stroke-linejoin="round"/>`;236    for (const p of pts) {237      const val = p.mbps >= 1000 ? (p.mbps / 1000).toFixed(1) + " GB/s" : Math.round(p.mbps) + " MB/s";238      dots += `<circle cx="${xPos(p.block).toFixed(1)}" cy="${yPos(p.mbps).toFixed(1)}" r="4.5" fill="${c}" stroke="#fcfcfb" stroke-width="2" class="pt" data-tip="QD${qd} · ${p.block >= 1048576 ? "1 MiB" : p.block / 1024 + " KiB"} random (cold): ${val}"/>`;239    }240    const last = pts[pts.length - 1];241    labels += `<text x="${xPos(last.block) + 12}" y="${yPos(last.mbps) + 4}" class="series-label" fill="${c}">QD ${qd}</text>`;242  }243  return `<figure class="chart-fig">244<svg viewBox="0 0 ${W} ${H}" role="img" aria-label="Cold random SSD read throughput by block size and queue depth, log scale">245<line x1="${ML}" y1="${MT + ih}" x2="${ML + iw}" y2="${MT + ih}" class="axis"/>246${g}${xticks}${lines}${dots}${labels}247<text x="${ML + iw / 2}" y="${H - 6}" class="axis-title" text-anchor="middle">read block size (random, F_NOCACHE cold, log–log)</text>248</svg>249<div id="chart-tip" class="chart-tip" hidden></div>250<figcaption>Measured on this project's M5 Max (expH, run ${esc(chart.run)}, 3 repeats/cell, iostat-validated ceiling ≈ 13.1 GB/s). Lines: reader queue depth.</figcaption>251</figure>`;252}253254module.exports = { esc, markdownToHtml, tocHtml, docHeader, logTimeline, highlightFile, layout, ssdChartSvg };255