Download app/micro_distillv2.html from webxos/microd_v2: direct link, hf CLI and curl.
- Browser
- Download file 99.8 kB
-
https://huggingface.co/webxos/microd_v2/resolve/main/app/micro_distillv2.html
- Command line
-
hf download hf://webxos/microd_v2/app/micro_distillv2.html
-
curl -L -o micro_distillv2.html https://huggingface.co/webxos/microd_v2/resolve/main/app/micro_distillv2.html
99.8 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <meta name="theme-color" content="#000000"> | |
| <meta name="description" content="Micro Distillery — build, train, and export a small language model from free open data. HuggingFace-ready .zip bundle."> | |
| <title>Micro Distillery</title> | |
| <link rel="manifest" href="manifest.json"> | |
| <link rel="apple-touch-icon" href="icon-192.png"> | |
| <style> | |
| :root { | |
| --bg: #000; | |
| --panel: #0a0a0a; | |
| --panel-2: #111; | |
| --panel-3: #161616; | |
| --border: #222; | |
| --border-hi: #333; | |
| --border-focus: #555; | |
| --text: #e5e5e5; | |
| --text-dim: #9a9a9a; | |
| --text-mute: #5f5f5f; | |
| --white: #fff; | |
| --err: #ef4444; | |
| --radius: 4px; | |
| --mono: 'SF Mono', Monaco, 'Cascadia Code', 'Courier New', monospace; | |
| --sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
| } | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| html, body { background: var(--bg); color: var(--text); } | |
| body { | |
| font-family: var(--sans); | |
| font-size: 13px; | |
| line-height: 1.5; | |
| min-height: 100vh; | |
| -webkit-font-smoothing: antialiased; | |
| } | |
| button { font-family: inherit; cursor: pointer; border: none; background: none; color: inherit; } | |
| input, select, textarea { font-family: inherit; } | |
| .hdr { | |
| border-bottom: 1px solid var(--border); | |
| padding: 12px 20px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 16px; | |
| position: sticky; | |
| top: 0; | |
| background: rgba(0,0,0,0.95); | |
| backdrop-filter: blur(8px); | |
| z-index: 50; | |
| flex-wrap: wrap; | |
| } | |
| .brand { display: flex; align-items: center; gap: 10px; } | |
| .brand-mark { | |
| font-family: var(--mono); | |
| font-size: 12px; | |
| font-weight: 700; | |
| color: var(--white); | |
| border: 1px solid var(--border-hi); | |
| padding: 4px 8px; | |
| border-radius: 3px; | |
| letter-spacing: 0.05em; | |
| } | |
| .brand-name { font-weight: 600; font-size: 14px; letter-spacing: -0.01em; } | |
| .brand-version { font-family: var(--mono); font-size: 11px; color: var(--text-mute); } | |
| .hdr-actions { display: flex; gap: 8px; flex-wrap: wrap; } | |
| .btn { | |
| font-size: 12px; | |
| font-weight: 500; | |
| padding: 8px 14px; | |
| border: 1px solid var(--border-hi); | |
| border-radius: var(--radius); | |
| background: var(--panel); | |
| color: var(--text); | |
| transition: all 0.15s ease; | |
| white-space: nowrap; | |
| } | |
| .btn:hover:not(:disabled) { background: var(--panel-2); border-color: var(--border-focus); } | |
| .btn:active:not(:disabled) { background: var(--panel-3); } | |
| .btn:disabled { opacity: 0.35; cursor: not-allowed; } | |
| .btn-primary { background: var(--white); color: #000; border-color: var(--white); font-weight: 600; } | |
| .btn-primary:hover:not(:disabled) { background: #d4d4d4; border-color: #d4d4d4; } | |
| .btn-block { width: 100%; display: block; } | |
| .btn-sm { padding: 6px 10px; font-size: 11px; } | |
| .btn-lg { padding: 12px 20px; font-size: 13px; } | |
| .btn-ghost { background: transparent; border-color: var(--border); color: var(--text-dim); } | |
| .btn-ghost:hover:not(:disabled) { color: var(--text); border-color: var(--border-hi); } | |
| .main { | |
| max-width: 1440px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| display: grid; | |
| grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr); | |
| gap: 20px; | |
| align-items: start; | |
| } | |
| @media (max-width: 900px) { .main { grid-template-columns: 1fr; } } | |
| .col { display: flex; flex-direction: column; gap: 20px; min-width: 0; } | |
| .panel { | |
| background: var(--panel); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius); | |
| overflow: hidden; | |
| } | |
| .panel-hdr { | |
| padding: 12px 16px; | |
| border-bottom: 1px solid var(--border); | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 10px; | |
| } | |
| .panel-title { | |
| font-size: 12px; | |
| font-weight: 600; | |
| letter-spacing: 0.02em; | |
| text-transform: uppercase; | |
| color: var(--text-dim); | |
| } | |
| .panel-body { padding: 16px; } | |
| .pipeline { display: flex; flex-direction: column; } | |
| .step { | |
| padding: 14px 16px; | |
| border-bottom: 1px solid var(--border); | |
| display: flex; | |
| gap: 12px; | |
| align-items: flex-start; | |
| } | |
| .step:last-child { border-bottom: none; } | |
| .step-num { | |
| flex-shrink: 0; | |
| width: 24px; | |
| height: 24px; | |
| border-radius: 50%; | |
| border: 1px solid var(--border-hi); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-family: var(--mono); | |
| font-size: 11px; | |
| font-weight: 600; | |
| color: var(--text-dim); | |
| transition: all 0.2s; | |
| } | |
| .step.active .step-num { border-color: var(--white); background: var(--white); color: #000; } | |
| .step.done .step-num { background: var(--white); border-color: var(--white); color: #000; } | |
| .step-body { flex: 1; min-width: 0; } | |
| .step-title { font-size: 13px; font-weight: 600; margin-bottom: 2px; } | |
| .step-desc { font-size: 12px; color: var(--text-dim); margin-bottom: 10px; } | |
| .step-actions { display: flex; gap: 8px; flex-wrap: wrap; } | |
| .setting { margin-bottom: 14px; } | |
| .setting:last-child { margin-bottom: 0; } | |
| .setting-label { display: flex; justify-content: space-between; font-size: 12px; margin-bottom: 6px; color: var(--text-dim); } | |
| .setting-val { font-family: var(--mono); color: var(--text); font-size: 11px; } | |
| input[type="range"] { | |
| width: 100%; height: 2px; background: var(--border-hi); border-radius: 1px; | |
| outline: none; -webkit-appearance: none; appearance: none; | |
| } | |
| input[type="range"]::-webkit-slider-thumb { | |
| -webkit-appearance: none; width: 14px; height: 14px; background: var(--white); | |
| border-radius: 50%; cursor: pointer; border: 2px solid var(--bg); | |
| } | |
| input[type="range"]::-moz-range-thumb { | |
| width: 14px; height: 14px; background: var(--white); border-radius: 50%; | |
| cursor: pointer; border: 2px solid var(--bg); | |
| } | |
| select { | |
| width: 100%; background: var(--panel-2); color: var(--text); | |
| border: 1px solid var(--border-hi); border-radius: var(--radius); | |
| padding: 7px 10px; font-size: 12px; outline: none; | |
| } | |
| select:focus { border-color: var(--border-focus); } | |
| .metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; } | |
| #osc-display { grid-template-columns: repeat(4, 1fr); } | |
| .metric { | |
| padding: 10px 8px; background: var(--panel-2); border: 1px solid var(--border); | |
| border-radius: 3px; text-align: center; | |
| } | |
| .metric-label { font-size: 10px; color: var(--text-mute); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 3px; } | |
| .metric-value { font-family: var(--mono); font-size: 14px; font-weight: 600; color: var(--white); } | |
| .tabs { display: flex; border-bottom: 1px solid var(--border); background: var(--panel-2); } | |
| .tab { | |
| padding: 10px 16px; font-size: 12px; color: var(--text-mute); | |
| border-bottom: 2px solid transparent; transition: all 0.15s; | |
| } | |
| .tab:hover { color: var(--text-dim); } | |
| .tab.active { color: var(--white); border-bottom-color: var(--white); } | |
| .tabpanel { display: none; } | |
| .tabpanel.active { display: block; } | |
| .terminal { | |
| height: 300px; overflow-y: auto; padding: 12px 14px; background: #000; | |
| font-family: var(--mono); font-size: 11.5px; line-height: 1.55; color: var(--text-dim); | |
| } | |
| .log-line { margin-bottom: 1px; } | |
| .log-line .ts { color: var(--text-mute); margin-right: 8px; } | |
| .log-line.ok { color: var(--text); } | |
| .log-line.warn { color: #a1a1aa; } | |
| .log-line.err { color: var(--err); } | |
| #kg-canvas { display: block; width: 100%; height: 300px; background: #000; } | |
| .graph-tools { | |
| display: flex; gap: 6px; padding: 10px 14px; | |
| border-top: 1px solid var(--border); background: var(--panel-2); flex-wrap: wrap; | |
| } | |
| .graph-tools .btn { flex: 1; min-width: 90px; padding: 6px 8px; font-size: 11px; } | |
| .api-log { | |
| height: 300px; overflow-y: auto; padding: 12px 14px; background: #000; | |
| font-family: var(--mono); font-size: 11.5px; line-height: 1.55; | |
| white-space: pre-wrap; | |
| } | |
| .api-line { padding: 1px 0; color: var(--text-dim); } | |
| .api-line .ts { color: var(--text-mute); margin-right: 8px; } | |
| .api-line.ok { color: var(--text); } | |
| .api-line.err { color: var(--err); } | |
| .progress { height: 3px; background: var(--border); border-radius: 2px; overflow: hidden; } | |
| .progress-fill { height: 100%; background: var(--white); width: 0%; transition: width 0.3s ease; } | |
| .progress-meta { | |
| display: flex; justify-content: space-between; font-size: 11px; | |
| color: var(--text-mute); margin-top: 6px; font-family: var(--mono); | |
| } | |
| .modal { | |
| position: fixed; inset: 0; background: rgba(0,0,0,0.85); backdrop-filter: blur(6px); | |
| display: flex; align-items: center; justify-content: center; z-index: 100; | |
| padding: 20px; opacity: 0; pointer-events: none; transition: opacity 0.2s ease; | |
| } | |
| .modal.show { opacity: 1; pointer-events: auto; } | |
| .modal.hidden { display: none; } | |
| .modal-box { | |
| background: var(--panel); border: 1px solid var(--border-hi); border-radius: 8px; | |
| width: 100%; max-width: 640px; max-height: 90vh; display: flex; flex-direction: column; | |
| overflow: hidden; box-shadow: 0 30px 80px rgba(0,0,0,0.8); | |
| } | |
| .modal-box.wide { max-width: 980px; } | |
| .modal-hdr { | |
| padding: 18px 22px; border-bottom: 1px solid var(--border); | |
| display: flex; justify-content: space-between; align-items: center; gap: 12px; | |
| } | |
| .modal-title { font-size: 14px; font-weight: 600; } | |
| .modal-close { | |
| width: 28px; height: 28px; border-radius: 4px; display: flex; | |
| align-items: center; justify-content: center; color: var(--text-dim); | |
| font-size: 18px; line-height: 1; | |
| } | |
| .modal-close:hover { background: var(--panel-2); color: var(--text); } | |
| .modal-body { padding: 22px; overflow-y: auto; flex: 1; } | |
| .modal-foot { | |
| padding: 16px 22px; border-top: 1px solid var(--border); | |
| display: flex; justify-content: space-between; align-items: center; | |
| gap: 12px; flex-wrap: wrap; | |
| } | |
| .wiz-header { padding: 20px 24px 0; } | |
| .wiz-progress { display: flex; gap: 4px; margin-bottom: 20px; } | |
| .wiz-dot { flex: 1; height: 3px; background: var(--border); border-radius: 2px; } | |
| .wiz-dot.active { background: var(--white); } | |
| .wiz-dot.done { background: var(--text-mute); } | |
| .wiz-title { font-size: 22px; font-weight: 600; margin-bottom: 6px; letter-spacing: -0.02em; } | |
| .wiz-sub { font-size: 13px; color: var(--text-dim); margin-bottom: 20px; } | |
| .wiz-body { padding: 0 24px 22px; } | |
| .wiz-body p { margin-bottom: 12px; color: var(--text-dim); font-size: 13px; line-height: 1.6; } | |
| .wiz-body ul { margin: 12px 0 12px 22px; color: var(--text-dim); font-size: 13px; } | |
| .wiz-body li { margin-bottom: 6px; } | |
| .wiz-body code { | |
| font-family: var(--mono); font-size: 11.5px; background: var(--panel-2); | |
| padding: 2px 6px; border-radius: 3px; border: 1px solid var(--border); | |
| } | |
| .wiz-info { | |
| background: var(--panel-2); border: 1px solid var(--border); | |
| border-radius: var(--radius); padding: 12px 14px; | |
| font-size: 12px; color: var(--text-dim); margin: 14px 0; | |
| } | |
| .wiz-info strong { color: var(--text); font-weight: 600; } | |
| .wiz-actions { | |
| padding: 16px 24px; border-top: 1px solid var(--border); | |
| display: flex; justify-content: space-between; gap: 10px; flex-wrap: wrap; | |
| } | |
| .wiz-actions .btn-primary { padding: 12px 24px; font-size: 14px; } | |
| .file-list { | |
| border: 1px solid var(--border); border-radius: var(--radius); | |
| overflow: hidden; max-height: 240px; overflow-y: auto; | |
| } | |
| .file-row { | |
| display: flex; align-items: center; gap: 10px; padding: 9px 12px; | |
| border-bottom: 1px solid var(--border); font-family: var(--mono); | |
| font-size: 12px; cursor: pointer; | |
| } | |
| .file-row:last-child { border-bottom: none; } | |
| .file-row:hover { background: var(--panel-2); } | |
| .file-row.selected { background: var(--panel-3); } | |
| .file-row-name { flex: 1; color: var(--text); } | |
| .file-row-size { color: var(--text-mute); font-size: 11px; } | |
| .file-preview { | |
| background: #000; border: 1px solid var(--border); border-radius: var(--radius); | |
| padding: 14px; margin-top: 14px; font-family: var(--mono); | |
| font-size: 11px; line-height: 1.5; color: var(--text-dim); | |
| max-height: 260px; overflow: auto; white-space: pre-wrap; word-break: break-word; | |
| } | |
| .opts-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 14px; } | |
| .opt { | |
| display: flex; align-items: center; gap: 8px; padding: 8px 10px; | |
| background: var(--panel-2); border: 1px solid var(--border); | |
| border-radius: 3px; font-size: 12px; color: var(--text-dim); cursor: pointer; | |
| } | |
| .opt:hover { border-color: var(--border-hi); color: var(--text); } | |
| .opt input { accent-color: var(--white); } | |
| .opt input:checked + span { color: var(--text); } | |
| .dot { | |
| display: inline-block; width: 6px; height: 6px; border-radius: 50%; | |
| background: var(--text-mute); margin-right: 6px; vertical-align: middle; | |
| } | |
| .dot.ok { background: #22c55e; } | |
| .dot.err { background: var(--err); } | |
| .hidden { display: none ; } | |
| .mt-16 { margin-top: 16px; } | |
| .mt-8 { margin-top: 8px; } | |
| .text-dim { color: var(--text-dim); } | |
| .text-mute { color: var(--text-mute); } | |
| .mono { font-family: var(--mono); } | |
| .small { font-size: 11px; } | |
| .offline-bar { | |
| position: fixed; bottom: 12px; right: 12px; background: var(--panel); | |
| border: 1px solid var(--err); color: var(--err); padding: 8px 14px; | |
| border-radius: var(--radius); font-size: 11px; z-index: 90; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header class="hdr"> | |
| <div class="brand"> | |
| <span class="brand-mark">mD</span> | |
| <span class="brand-name">Micro Distillery</span> | |
| <span class="brand-version">v3.1.2</span> | |
| </div> | |
| <div class="hdr-actions"> | |
| <button class="btn btn-ghost" id="btn-wizard">Setup Wizard</button> | |
| <button class="btn" id="btn-reset">Reset</button> | |
| <button class="btn btn-primary" id="btn-export">Export Bundle</button> | |
| </div> | |
| </header> | |
| <main class="main"> | |
| <div class="col"> | |
| <section class="panel"> | |
| <div class="panel-hdr"> | |
| <span class="panel-title">Pipeline</span> | |
| <span class="small mono text-mute" id="pipeline-status">idle</span> | |
| </div> | |
| <div class="pipeline"> | |
| <div class="step" id="step-fetch"> | |
| <div class="step-num">1</div> | |
| <div class="step-body"> | |
| <div class="step-title">Fetch Training Data</div> | |
| <div class="step-desc">Pull corpora and model cards from HuggingFace + GitHub (2026 sources).</div> | |
| <div class="step-actions"> | |
| <button class="btn btn-sm" id="btn-fetch">Fetch</button> | |
| <span class="small text-mute" id="fetch-status" style="align-self:center;"></span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="step" id="step-train"> | |
| <div class="step-num">2</div> | |
| <div class="step-body"> | |
| <div class="step-title">Train Model</div> | |
| <div class="step-desc">GRPO policy optimization + VAE filter on a bigram model.</div> | |
| <div class="step-actions"> | |
| <button class="btn btn-sm" id="btn-train">Train</button> | |
| <button class="btn btn-sm btn-ghost" id="btn-stop" disabled>Stop</button> | |
| <span class="small text-mute" id="train-status" style="align-self:center;"></span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="step" id="step-export"> | |
| <div class="step-num">3</div> | |
| <div class="step-body"> | |
| <div class="step-title">Export Bundle</div> | |
| <div class="step-desc">Download a HuggingFace-ready .zip with model, tests, diagrams.</div> | |
| <div class="step-actions"> | |
| <button class="btn btn-sm" id="btn-export-2">Export</button> | |
| <span class="small text-mute" id="export-status" style="align-self:center;"></span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <section class="panel"> | |
| <div class="panel-hdr"><span class="panel-title">Metrics</span></div> | |
| <div class="panel-body"> | |
| <div class="metrics"> | |
| <div class="metric"><div class="metric-label">Steps</div><div class="metric-value" id="m-steps">0</div></div> | |
| <div class="metric"><div class="metric-label">Loss</div><div class="metric-value" id="m-loss">—</div></div> | |
| <div class="metric"><div class="metric-label">Reward</div><div class="metric-value" id="m-reward">—</div></div> | |
| </div> | |
| <div class="metrics mt-8"> | |
| <div class="metric"><div class="metric-label">Vocab</div><div class="metric-value" id="m-vocab">0</div></div> | |
| <div class="metric"><div class="metric-label">States</div><div class="metric-value" id="m-states">0</div></div> | |
| <div class="metric"><div class="metric-label">Cache Hit</div><div class="metric-value" id="m-cache">0%</div></div> | |
| </div> | |
| </div> | |
| </section> | |
| <section class="panel"> | |
| <div class="panel-hdr"><span class="panel-title">Settings</span></div> | |
| <div class="panel-body"> | |
| <div class="setting"> | |
| <div class="setting-label"><span>Group Size</span><span class="setting-val" id="val-group">8</span></div> | |
| <input type="range" id="cfg-group" min="4" max="32" step="2" value="8"> | |
| </div> | |
| <div class="setting"> | |
| <div class="setting-label"><span>KL Penalty</span><span class="setting-val" id="val-kl">0.100</span></div> | |
| <input type="range" id="cfg-kl" min="0.01" max="0.5" step="0.01" value="0.1"> | |
| </div> | |
| <div class="setting"> | |
| <div class="setting-label"><span>VAE Latent Dim (metadata)</span><span class="setting-val" id="val-latent">32</span></div> | |
| <input type="range" id="cfg-latent" min="8" max="128" step="8" value="32"> | |
| </div> | |
| <div class="setting"> | |
| <div class="setting-label"><span>Filter Threshold (metadata)</span><span class="setting-val" id="val-filter">0.70</span></div> | |
| <input type="range" id="cfg-filter" min="0.1" max="0.9" step="0.05" value="0.7"> | |
| </div> | |
| <div class="setting"> | |
| <div class="setting-label"><span>Reference Architecture</span></div> | |
| <select id="cfg-arch"> | |
| <option value="tiny-bigram" selected>Tiny Bigram (default, ~1 KB)</option> | |
| <option value="smollm2-135m">SmolLM2-135M (HuggingFaceTB)</option> | |
| <option value="qwen25-05b">Qwen2.5-0.5B (Qwen)</option> | |
| <option value="olmo2-1b">OLMo-2-1B (AllenAI)</option> | |
| <option value="tinyllama-11b">TinyLlama-1.1B (TinyLlama)</option> | |
| </select> | |
| <div class="small text-mute mt-8">Reference only — metadata used in export. Actual model is character-level bigram. GRPO training uses group size and KL penalty; VAE fields are recorded in export metadata only.</div> | |
| </div> | |
| </div> | |
| </section> | |
| </div> | |
| <div class="col"> | |
| <section class="panel"> | |
| <div class="tabs"> | |
| <button class="tab active" data-tab="terminal">Console</button> | |
| <button class="tab" data-tab="graph">Knowledge Graph</button> | |
| <button class="tab" data-tab="api">API Log</button> | |
| </div> | |
| <div class="tabpanel active" data-tabpanel="terminal"> | |
| <div class="terminal" id="terminal"></div> | |
| </div> | |
| <div class="tabpanel" data-tabpanel="graph"> | |
| <canvas id="kg-canvas"></canvas> | |
| <div class="graph-tools"> | |
| <button class="btn btn-sm" id="btn-graph-render">Re-render</button> | |
| <button class="btn btn-sm" id="btn-graph-save">Save to IDB</button> | |
| <button class="btn btn-sm" id="btn-graph-load">Load from IDB</button> | |
| <button class="btn btn-sm" id="btn-graph-clear">Clear</button> | |
| </div> | |
| </div> | |
| <div class="tabpanel" data-tabpanel="api"> | |
| <div class="api-log" id="api-log">[api-fanout] Idle. Click "Fetch" in the pipeline to begin. | |
| [api-fanout] Endpoints (2026, HuggingFace + GitHub only): | |
| raw.githubusercontent.com/karpathy/makemore/master/names.txt corpus | |
| huggingface.co/api/models?search=smollm2&limit=10 SmolLM2 models | |
| huggingface.co/api/models?search=qwen2.5-0.5b&limit=5 Qwen2.5 models | |
| huggingface.co/HuggingFaceTB/SmolLM2-135M/raw/main/README.md SmolLM2 card | |
| huggingface.co/allenai/OLMo-2-0425-1B/raw/main/README.md OLMo-2 card | |
| api.github.com/repos/karpathy/makemore Karpathy repo | |
| </div> | |
| </div> | |
| </section> | |
| <section class="panel"> | |
| <div class="panel-hdr"> | |
| <span class="panel-title">Progress</span> | |
| <span class="small mono text-mute" id="progress-label">idle</span> | |
| </div> | |
| <div class="panel-body"> | |
| <div class="progress"><div class="progress-fill" id="progress-fill"></div></div> | |
| <div class="progress-meta"> | |
| <span id="progress-task">—</span> | |
| <span id="progress-pct">0%</span> | |
| </div> | |
| </div> | |
| </section> | |
| <section class="panel"> | |
| <div class="panel-hdr"> | |
| <span class="panel-title">4x Harmonic Oscillator</span> | |
| <span class="small mono text-mute" id="osc-status">superposition</span> | |
| </div> | |
| <div class="panel-body"> | |
| <div class="metrics" id="osc-display"></div> | |
| <div class="mt-16" style="display:flex; gap:8px; flex-wrap:wrap;"> | |
| <button class="btn btn-sm" id="btn-osc-measure">Measure</button> | |
| <span class="small text-mute" style="align-self:center;" id="osc-result"></span> | |
| </div> | |
| <div class="small text-mute mt-8"> | |
| Four incommensurate frequencies (1, phi, 2, pi). Coherent equal-weight superposition — |c_i|² is invariant under Hamiltonian evolution, so probabilities stay at 0.25 each. Measurement collapse drives the export seed. | |
| </div> | |
| </div> | |
| </section> | |
| </div> | |
| </main> | |
| <div class="modal hidden" id="wizard"> | |
| <div class="modal-box"> | |
| <div class="wiz-header"> | |
| <div class="wiz-progress" id="wiz-progress"></div> | |
| <div class="wiz-title" id="wiz-title">Welcome</div> | |
| <div class="wiz-sub" id="wiz-sub">Build a small language model in 3 steps.</div> | |
| </div> | |
| <div class="modal-body" style="padding: 0 24px 0;"> | |
| <div class="wiz-body" id="wiz-content"></div> | |
| </div> | |
| <div class="wiz-actions"> | |
| <button class="btn btn-ghost" id="wiz-back">Back</button> | |
| <div style="display:flex; gap:8px;"> | |
| <button class="btn btn-ghost" id="wiz-skip">Skip wizard</button> | |
| <button class="btn btn-primary" id="wiz-next">Begin</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="modal hidden" id="export-modal"> | |
| <div class="modal-box wide"> | |
| <div class="modal-hdr"> | |
| <div> | |
| <div class="modal-title">Export Bundle</div> | |
| <div class="small text-mute" style="margin-top:2px;">HuggingFace-ready .zip</div> | |
| </div> | |
| <button class="modal-close" data-close="export-modal">x</button> | |
| </div> | |
| <div class="modal-body"> | |
| <div style="display:grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr); gap:20px;"> | |
| <div> | |
| <div class="small text-mute" style="margin-bottom:8px; text-transform:uppercase; letter-spacing:0.05em;">Bundle Contents</div> | |
| <div class="file-list" id="file-list"></div> | |
| </div> | |
| <div> | |
| <div class="small text-mute" style="margin-bottom:8px; text-transform:uppercase; letter-spacing:0.05em;">Preview</div> | |
| <div class="file-preview" id="file-preview">Select a file to preview.</div> | |
| </div> | |
| </div> | |
| <div class="mt-16"> | |
| <div class="small text-mute" style="margin-bottom:8px; text-transform:uppercase; letter-spacing:0.05em;">Options</div> | |
| <div class="opts-grid"> | |
| <label class="opt"><input type="checkbox" id="opt-config" checked><span>Include config.json</span></label> | |
| <label class="opt"><input type="checkbox" id="opt-vocab" checked><span>Include vocab.json</span></label> | |
| <label class="opt"><input type="checkbox" id="opt-logs" checked><span>Include training logs</span></label> | |
| <label class="opt"><input type="checkbox" id="opt-kg" checked><span>Include knowledge graph</span></label> | |
| <label class="opt"><input type="checkbox" id="opt-diagrams" checked><span>Include diagrams</span></label> | |
| <label class="opt"><input type="checkbox" id="opt-parquet" checked><span>Include to_parquet.py</span></label> | |
| </div> | |
| </div> | |
| <div class="wiz-info" style="margin-top:16px;"> | |
| <strong>Ready for Hugging Face.</strong> Includes <code>README.md</code> model card, <code>config.json</code>, <code>model.json</code>, <code>vocab.json</code>, <code>test.py</code>, <code>test.sh</code>, <code>test_readme.md</code>, and <code>diagrams/</code>. Upload the .zip contents to any HF repo. | |
| </div> | |
| </div> | |
| <div class="modal-foot"> | |
| <span class="small mono text-mute" id="export-size">—</span> | |
| <div style="display:flex; gap:8px;"> | |
| <button class="btn btn-ghost" data-close="export-modal">Cancel</button> | |
| <button class="btn btn-primary" id="btn-download">Download .zip</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="offline-bar hidden" id="offline-bar">Offline — using cached data</div> | |
| <script src="https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/file-saver@2.0.5/dist/FileSaver.min.js"></script> | |
| <script> | |
| ; | |
| const $ = (id) => document.getElementById(id); | |
| const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); | |
| // Production: suppress info/ok logs, keep warn + err visible in the Console / API panes. | |
| // Success feedback is provided by step markers, progress bar, metrics, and status chips. | |
| const LOG_ONLY_ERRORS = true; | |
| function factorial(n) { | |
| if (n <= 1) return 1; | |
| let r = 1; | |
| for (let i = 2; i <= n; i++) r *= i; | |
| return r; | |
| } | |
| function hermite(n, x) { | |
| if (n === 0) return 1; | |
| if (n === 1) return 2 * x; | |
| let h0 = 1, h1 = 2 * x; | |
| for (let k = 2; k <= n; k++) { | |
| const h2 = 2 * x * h1 - 2 * (k - 1) * h0; | |
| h0 = h1; h1 = h2; | |
| } | |
| return h1; | |
| } | |
| function makeRNG(seed) { | |
| let s = (seed >>> 0) || 1; | |
| return () => { | |
| s ^= s << 13; s >>>= 0; | |
| s ^= s >>> 17; | |
| s ^= s << 5; s >>>= 0; | |
| return s / 0xFFFFFFFF; | |
| }; | |
| } | |
| function humanSize(bytes) { | |
| if (bytes < 1024) return bytes + " B"; | |
| if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB"; | |
| return (bytes / (1024 * 1024)).toFixed(2) + " MB"; | |
| } | |
| function escapeHtml(s) { | |
| return String(s).replace(/[&<>"']/g, (c) => ({ | |
| "&": "&", "<": "<", ">": ">", '"': """, "'": "'" | |
| }[c])); | |
| } | |
| class HarmonicOscillator { | |
| constructor(omega, n = 0, hbar = 1, mass = 1) { | |
| this.omega = omega; | |
| this.n = n; | |
| this.hbar = hbar; | |
| this.mass = mass; | |
| this.alpha = Math.sqrt(mass * omega / hbar); | |
| this._cdfCache = null; | |
| } | |
| energy() { return this.hbar * this.omega * (this.n + 0.5); } | |
| psi(x) { | |
| const xi = this.alpha * x; | |
| const H = hermite(this.n, xi); | |
| const norm = Math.pow(this.alpha / Math.PI, 0.25) / | |
| Math.sqrt(Math.pow(2, this.n) * factorial(this.n)); | |
| return norm * H * Math.exp(-xi * xi / 2); | |
| } | |
| psi2(x) { const p = this.psi(x); return p * p; } | |
| _buildCdf() { | |
| if (this._cdfCache) return this._cdfCache; | |
| const N = 1024; | |
| const xmin = -15 / this.alpha; | |
| const xmax = 15 / this.alpha; | |
| const dx = (xmax - xmin) / N; | |
| const pdf = new Float64Array(N + 1); | |
| let total = 0; | |
| for (let i = 0; i <= N; i++) { | |
| const x = xmin + i * dx; | |
| const p = this.psi2(x); | |
| pdf[i] = p; | |
| total += p; | |
| } | |
| const cdf = new Float64Array(N + 1); | |
| let acc = 0; | |
| for (let i = 0; i <= N; i++) { acc += pdf[i]; cdf[i] = acc / total; } | |
| this._cdfCache = { xmin, xmax, N, cdf }; | |
| return this._cdfCache; | |
| } | |
| samplePosition(rng) { | |
| const { xmin, xmax, N, cdf } = this._buildCdf(); | |
| const r = rng(); | |
| let lo = 0, hi = N; | |
| while (lo < hi) { | |
| const mid = (lo + hi) >> 1; | |
| if (cdf[mid] < r) lo = mid + 1; | |
| else hi = mid; | |
| } | |
| const dx = (xmax - xmin) / N; | |
| return xmin + lo * dx; | |
| } | |
| } | |
| class Superposition4 { | |
| constructor(seed = 1337) { | |
| const freqs = [1.0, 1.618033988749895, 2.0, 3.141592653589793]; | |
| this.oscillators = freqs.map((w, i) => new HarmonicOscillator(w, i)); | |
| this.amplitudes = [0.5, 0.5, 0.5, 0.5]; | |
| this.phases = [0, Math.PI / 2, Math.PI, 3 * Math.PI / 2]; | |
| this.t = 0; | |
| this.hbar = 1; | |
| this._rng = makeRNG(seed); | |
| } | |
| coefficients(t = this.t) { | |
| return this.oscillators.map((osc, i) => { | |
| const E = osc.energy(); | |
| const phase = this.phases[i] - E * t / this.hbar; | |
| return { re: this.amplitudes[i] * Math.cos(phase), im: this.amplitudes[i] * Math.sin(phase) }; | |
| }); | |
| } | |
| probabilities(t = this.t) { | |
| const c = this.coefficients(t); | |
| const p = c.map((x) => x.re * x.re + x.im * x.im); | |
| const sum = p.reduce((a, b) => a + b, 0) || 1; | |
| return p.map((x) => x / sum); | |
| } | |
| evolve(dt) { this.t += dt; return this.t; } | |
| measure() { | |
| const probs = this.probabilities(); | |
| const r = this._rng(); | |
| let chosen = 0, acc = 0; | |
| for (let i = 0; i < probs.length; i++) { acc += probs[i]; if (r <= acc) { chosen = i; break; } } | |
| const x = this.oscillators[chosen].samplePosition(this._rng); | |
| return { oscillator: chosen, x, energy: this.oscillators[chosen].energy(), probabilities: probs, t: this.t }; | |
| } | |
| generateMeasurements(count, dt = 0.1) { | |
| const out = []; | |
| for (let i = 0; i < count; i++) { this.evolve(dt); out.push(this.measure()); } | |
| return out; | |
| } | |
| } | |
| class KnowledgeGraph { | |
| constructor() { this.nodes = new Map(); this.edges = new Map(); } | |
| clear() { this.nodes.clear(); this.edges.clear(); } | |
| addNode(id, data = {}) { | |
| if (!this.nodes.has(id)) { | |
| // New node: start with base weight 1; if data.weight provided, it replaces base. | |
| this.nodes.set(id, Object.assign({ id, weight: 1 }, data)); | |
| } else { | |
| // Existing node: accumulate weight from data onto current weight, then apply other fields. | |
| const n = this.nodes.get(id); | |
| const prevW = (typeof n.weight === "number" ? n.weight : 1); | |
| const addW = (typeof data.weight === "number" ? data.weight : 0); | |
| Object.assign(n, data); | |
| n.weight = prevW + addW; | |
| } | |
| return this.nodes.get(id); | |
| } | |
| addEdge(source, target, weight = 1, label = "") { | |
| const id = source + "\u0001" + target; | |
| let e = this.edges.get(id); | |
| if (!e) { e = { id, source, target, weight: 0, label }; this.edges.set(id, e); } | |
| e.weight += weight; | |
| if (label) e.label = label; | |
| return e; | |
| } | |
| layout2D(seed) { | |
| const rng = makeRNG(seed); | |
| const nodes = [...this.nodes.values()]; | |
| const N = nodes.length; | |
| const baseR = 40 + Math.sqrt(N) * 20; | |
| nodes.forEach((n, i) => { | |
| const angle = (i / Math.max(1, N)) * Math.PI * 2 + rng() * 0.4; | |
| const r = baseR * (0.4 + 0.8 * Math.sqrt(rng())); | |
| n.x = Math.cos(angle) * r; | |
| n.y = Math.sin(angle) * r; | |
| }); | |
| return nodes; | |
| } | |
| toJSON() { return { nodes: [...this.nodes.values()], edges: [...this.edges.values()] }; } | |
| fromJSON(json) { | |
| this.clear(); | |
| for (const n of (json.nodes || [])) this.nodes.set(n.id, n); | |
| for (const e of (json.edges || [])) this.edges.set(e.id, e); | |
| } | |
| } | |
| class KGStorage { | |
| constructor() { this.db = null; } | |
| open() { | |
| if (this.db) return Promise.resolve(this.db); | |
| return new Promise((resolve, reject) => { | |
| const req = indexedDB.open("micro_distillery_db", 2); | |
| req.onupgradeneeded = (ev) => { | |
| const db = ev.target.result; | |
| if (!db.objectStoreNames.contains("knowledge_graph")) | |
| db.createObjectStore("knowledge_graph", { keyPath: "id" }); | |
| if (!db.objectStoreNames.contains("api_cache")) | |
| db.createObjectStore("api_cache", { keyPath: "url" }); | |
| if (!db.objectStoreNames.contains("models")) | |
| db.createObjectStore("models", { keyPath: "id" }); | |
| }; | |
| req.onsuccess = () => { this.db = req.result; resolve(this.db); }; | |
| req.onerror = () => reject(req.error); | |
| }); | |
| } | |
| async saveGraph(graph) { | |
| const db = await this.open(); | |
| return new Promise((resolve, reject) => { | |
| const tx = db.transaction("knowledge_graph", "readwrite"); | |
| const store = tx.objectStore("knowledge_graph"); | |
| store.clear(); | |
| const json = graph.toJSON(); | |
| for (const n of json.nodes) store.put(Object.assign({}, n, { type: "node" })); | |
| for (const e of json.edges) store.put(Object.assign({}, e, { type: "edge" })); | |
| tx.oncomplete = () => resolve(json.nodes.length + json.edges.length); | |
| tx.onerror = () => reject(tx.error); | |
| }); | |
| } | |
| async loadGraph() { | |
| const db = await this.open(); | |
| return new Promise((resolve, reject) => { | |
| const tx = db.transaction("knowledge_graph", "readonly"); | |
| const req = tx.objectStore("knowledge_graph").getAll(); | |
| req.onsuccess = () => { | |
| const items = req.result || []; | |
| const g = new KnowledgeGraph(); | |
| for (const item of items) { | |
| if (item.type === "node") g.nodes.set(item.id, item); | |
| else if (item.type === "edge") g.edges.set(item.id, item); | |
| } | |
| resolve(g); | |
| }; | |
| req.onerror = () => reject(req.error); | |
| }); | |
| } | |
| async clearGraph() { | |
| const db = await this.open(); | |
| return new Promise((resolve) => { | |
| const tx = db.transaction("knowledge_graph", "readwrite"); | |
| tx.objectStore("knowledge_graph").clear(); | |
| tx.oncomplete = () => resolve(true); | |
| tx.onerror = () => resolve(false); | |
| }); | |
| } | |
| async saveCache(url, data) { | |
| const db = await this.open(); | |
| return new Promise((resolve) => { | |
| const tx = db.transaction("api_cache", "readwrite"); | |
| tx.objectStore("api_cache").put({ url, data, timestamp: Date.now() }); | |
| tx.oncomplete = () => resolve(true); | |
| tx.onerror = () => resolve(false); | |
| }); | |
| } | |
| async saveModel(id, data) { | |
| const db = await this.open(); | |
| return new Promise((resolve) => { | |
| const tx = db.transaction("models", "readwrite"); | |
| tx.objectStore("models").put({ id, data, timestamp: Date.now() }); | |
| tx.oncomplete = () => resolve(true); | |
| tx.onerror = () => resolve(false); | |
| }); | |
| } | |
| } | |
| const FALLBACK_NAMES = [ | |
| "alice","bob","charlie","david","emma","frank","grace","henry","isabel","jack", | |
| "kate","liam","mia","noah","olivia","peter","quinn","rose","sam","tom", | |
| "uma","victor","wendy","xavier","yara","zoe","anna","ben","chloe","daniel", | |
| "ella","felix","gina","hugo","irene","james","karen","leo","maya","nina", | |
| "oscar","paul","rita","sophia","tara","ursula","vera","will","xia","yuki", | |
| "zara","adam","bella","carl","diana","eric","fiona","george","helen","ivan", | |
| "julia","kevin","laura","michael","nora","oliver","patricia","robert","sarah", | |
| "thomas","victoria","walter","yvonne","zach","arthur","beatrice","clara","derek", | |
| "edith","florence","gerald","harriet","isaac","josephine","kenneth","linda", | |
| "martin","nancy","oswald","phyllis","raymond","sylvia","theodore","ursula","vincent", | |
| "wanda","yolanda","abigail","brian","caroline","donald","elizabeth","frederick", | |
| "gwendolyn","howard","irene","jonathan","kathleen","leonard","margaret","nathaniel", | |
| "octavia","philip","rachel","stephen","theresa","ulysses","vanessa","wesley", | |
| "xena","yusuf","zachary","amelia","brandon","catherine","duncan","eleanor", | |
| "francis","gabriel","hannah","ian","jasmine","kyle","lucy","matthew","nicole", | |
| "owen","penelope","quentin","rebecca","sebastian","tiffany","upton","valerie" | |
| ]; | |
| class APIFanOut { | |
| constructor(onLog) { this.onLog = onLog; this.results = {}; } | |
| async fetchWithTimeout(url, timeoutMs, asJson) { | |
| const ctrl = new AbortController(); | |
| const timer = setTimeout(() => ctrl.abort(), timeoutMs); | |
| try { | |
| const resp = await fetch(url, { signal: ctrl.signal, mode: "cors" }); | |
| if (!resp.ok) throw new Error("HTTP " + resp.status); | |
| return asJson ? await resp.json() : await resp.text(); | |
| } finally { clearTimeout(timer); } | |
| } | |
| async fanOut(endpoints) { | |
| const results = await Promise.all(endpoints.map(async (ep) => { | |
| const t0 = performance.now(); | |
| try { | |
| const data = await this.fetchWithTimeout(ep.url, 8000, ep.type === "json"); | |
| const ms = Math.round(performance.now() - t0); | |
| const size = typeof data === "string" ? data.length : JSON.stringify(data).length; | |
| this.onLog(ep.name + " " + ms + "ms " + size + "B", "ok"); | |
| return { name: ep.name, ok: true, data }; | |
| } catch (e) { | |
| this.onLog(ep.name + " FAILED " + e.message, "err"); | |
| return { name: ep.name, ok: false, error: e.message }; | |
| } | |
| })); | |
| for (const r of results) if (r.ok) this.results[r.name] = r.data; | |
| return this.results; | |
| } | |
| getCorpus() { | |
| const raw = this.results["names_corpus"]; | |
| if (typeof raw === "string" && raw.length > 100) { | |
| return raw.split(/\r?\n/).map((s) => s.trim()).filter(Boolean); | |
| } | |
| return FALLBACK_NAMES.slice(); | |
| } | |
| } | |
| class Engine { | |
| constructor() { | |
| this.fetchDone = false; | |
| this.trainDone = false; | |
| this.fetching = false; | |
| this.training = false; | |
| this.transitions = new Map(); | |
| this.vocabCounts = new Map(); | |
| this.bos = "<|endoftext|>"; | |
| this.eos = "<|endoftext|>"; | |
| this.graph = new KnowledgeGraph(); | |
| this.storage = new KGStorage(); | |
| this.kgSeed = 42; | |
| this.superposition = new Superposition4(1337); | |
| this.quantumMeasurements = []; | |
| this.lastCollapse = null; | |
| this.apiFanOut = new APIFanOut((m, c) => this.apiLog(m, c)); | |
| this.corpus = FALLBACK_NAMES.slice(); | |
| // Persistent trajectory cache for cache hit/miss accounting across training steps. | |
| this.cache = new Map(); | |
| this.metrics = { | |
| trainingSteps: 0, | |
| finalLoss: 0, | |
| averageReward: 0, | |
| cacheHits: 0, | |
| cacheMisses: 0, | |
| filteredCount: 0, | |
| totalSamples: 0, | |
| vaeLoss: 0 | |
| }; | |
| this.modelFiles = []; | |
| this.selectedPreviewIndex = 0; | |
| } | |
| log(msg, cls = "") { | |
| if (LOG_ONLY_ERRORS && cls !== "warn" && cls !== "err") return; | |
| const term = $("terminal"); | |
| if (!term) return; | |
| const time = new Date().toLocaleTimeString("en-US", { hour12: false }); | |
| const line = document.createElement("div"); | |
| line.className = "log-line " + cls; | |
| line.innerHTML = '<span class="ts">' + time + '</span>' + escapeHtml(msg); | |
| term.appendChild(line); | |
| term.scrollTop = term.scrollHeight; | |
| while (term.children.length > 300) term.removeChild(term.firstChild); | |
| } | |
| apiLog(msg, cls = "") { | |
| if (LOG_ONLY_ERRORS && cls !== "err") return; | |
| const log = $("api-log"); | |
| if (!log) return; | |
| const time = new Date().toLocaleTimeString("en-US", { hour12: false }); | |
| const line = document.createElement("div"); | |
| line.className = "api-line " + cls; | |
| line.innerHTML = '<span class="ts">' + time + '</span>' + escapeHtml(msg); | |
| log.appendChild(line); | |
| log.scrollTop = log.scrollHeight; | |
| while (log.children.length > 300) log.removeChild(log.firstChild); | |
| } | |
| setProgress(pct, task) { | |
| const p = Math.max(0, Math.min(100, pct)); | |
| const fill = $("progress-fill"); | |
| if (fill) fill.style.width = p + "%"; | |
| const pctEl = $("progress-pct"); | |
| if (pctEl) pctEl.textContent = Math.round(p) + "%"; | |
| if (task !== undefined) { | |
| const taskEl = $("progress-task"); | |
| if (taskEl) taskEl.textContent = task; | |
| } | |
| } | |
| setProgressLabel(label) { | |
| const p = $("progress-label"); | |
| if (p) p.textContent = label; | |
| const ps = $("pipeline-status"); | |
| if (ps) ps.textContent = label; | |
| } | |
| updateMetrics() { | |
| $("m-steps").textContent = String(this.metrics.trainingSteps); | |
| $("m-loss").textContent = this.metrics.finalLoss > 0 ? this.metrics.finalLoss.toFixed(4) : "—"; | |
| $("m-reward").textContent = this.metrics.averageReward > 0 ? this.metrics.averageReward.toFixed(3) : "—"; | |
| $("m-vocab").textContent = String(this.vocabCounts.size); | |
| $("m-states").textContent = String(this.transitions.size); | |
| const total = this.metrics.cacheHits + this.metrics.cacheMisses; | |
| $("m-cache").textContent = total > 0 ? Math.round((this.metrics.cacheHits / total) * 100) + "%" : "0%"; | |
| } | |
| markStep(id, status, label) { | |
| const el = $("step-" + id); | |
| if (!el) return; | |
| el.classList.remove("active", "done"); | |
| if (status) el.classList.add(status); | |
| if (label !== undefined) { | |
| const statusEl = $({ fetch: "fetch-status", train: "train-status", export: "export-status" }[id]); | |
| if (statusEl) statusEl.textContent = label; | |
| } | |
| } | |
| async fetch() { | |
| if (this.fetching) return false; | |
| this.fetching = true; | |
| this.setProgressLabel("fetching"); | |
| this.setProgress(0, "Fetching open data..."); | |
| this.markStep("fetch", "active", "fetching..."); | |
| this.log("Starting data fetch from HuggingFace + GitHub (2026 sources)..."); | |
| const endpoints = [ | |
| { name: "names_corpus", url: "https://raw.githubusercontent.com/karpathy/makemore/master/names.txt", type: "text" }, | |
| { name: "hf_smollm", url: "https://huggingface.co/api/models?search=smollm2&limit=10", type: "json" }, | |
| { name: "hf_qwen", url: "https://huggingface.co/api/models?search=qwen2.5-0.5b&limit=5", type: "json" }, | |
| { name: "smollm_readme", url: "https://huggingface.co/HuggingFaceTB/SmolLM2-135M/raw/main/README.md", type: "text" }, | |
| { name: "olmo_readme", url: "https://huggingface.co/allenai/OLMo-2-0425-1B/raw/main/README.md", type: "text" }, | |
| { name: "makemore_repo", url: "https://api.github.com/repos/karpathy/makemore", type: "json" } | |
| ]; | |
| try { | |
| const results = await this.apiFanOut.fanOut(endpoints); | |
| this.setProgress(60, "Caching results..."); | |
| for (const [name, data] of Object.entries(results)) { | |
| try { await this.storage.saveCache(name, data); } catch (e) { /* ignored */ } | |
| } | |
| this.setProgress(75, "Building knowledge graph..."); | |
| this.corpus = this.apiFanOut.getCorpus(); | |
| this.log("Corpus loaded: " + this.corpus.length + " lines"); | |
| this.buildKnowledgeGraph(results); | |
| this.setProgress(90, "Seeding model..."); | |
| this.seedBigramModel(this.corpus); | |
| try { await this.storage.saveGraph(this.graph); } catch (e) { /* ignored */ } | |
| this.renderGraph(); | |
| this.updateMetrics(); | |
| this.fetchDone = true; | |
| this.setProgress(100, "Data ready"); | |
| this.setProgressLabel("ready"); | |
| this.markStep("fetch", "done", this.corpus.length + " lines"); | |
| this.markStep("train", "active"); | |
| this.log("Data fetch complete. Ready to train.", "ok"); | |
| this.apiLog("Fetch complete — corpus: " + this.corpus.length + " lines", "ok"); | |
| return true; | |
| } catch (e) { | |
| this.log("Fetch failed: " + e.message, "err"); | |
| this.apiLog("Fetch failed: " + e.message, "err"); | |
| this.markStep("fetch", "active", "failed"); | |
| this.setProgress(0, "Failed"); | |
| this.setProgressLabel("error"); | |
| this.corpus = FALLBACK_NAMES.slice(); | |
| this.seedBigramModel(this.corpus); | |
| this.buildKnowledgeGraph({}); | |
| this.renderGraph(); | |
| this.updateMetrics(); | |
| this.fetchDone = true; | |
| this.markStep("fetch", "done", "fallback (" + this.corpus.length + ")"); | |
| this.markStep("train", "active"); | |
| this.log("Using bundled fallback corpus instead.", "warn"); | |
| return true; | |
| } finally { | |
| this.fetching = false; | |
| } | |
| } | |
| buildKnowledgeGraph(apiResults) { | |
| this.graph = new KnowledgeGraph(); | |
| const charFreq = new Map(); | |
| const charTrans = new Map(); | |
| for (const line of this.corpus) { | |
| const chars = ("\u0001" + line + "\u0002").split(""); | |
| for (let i = 0; i < chars.length; i++) { | |
| charFreq.set(chars[i], (charFreq.get(chars[i]) || 0) + 1); | |
| if (i > 0) { | |
| const key = chars[i - 1] + "\u0001" + chars[i]; | |
| charTrans.set(key, (charTrans.get(key) || 0) + 1); | |
| } | |
| } | |
| } | |
| for (const [ch, freq] of charFreq) { | |
| const label = ch === "\u0001" ? "BOS" : (ch === "\u0002" ? "EOS" : ch); | |
| this.graph.addNode("char:" + ch, { label, weight: freq, kind: "char" }); | |
| } | |
| for (const [key, w] of charTrans) { | |
| const [a, b] = key.split("\u0001"); | |
| this.graph.addEdge("char:" + a, "char:" + b, w, "char-transition"); | |
| } | |
| this.graph.addNode("api:corpus", { label: "corpus", kind: "api", weight: 1 }); | |
| this.graph.addEdge("api:corpus", "char:\u0001", 1, "seeds"); | |
| if (apiResults["hf_smollm"] && Array.isArray(apiResults["hf_smollm"])) { | |
| apiResults["hf_smollm"].forEach((m, i) => { | |
| const id = "model:" + (m.id || ("smollm_" + i)); | |
| this.graph.addNode(id, { label: m.id || ("smollm_" + i), kind: "model", weight: 1 }); | |
| this.graph.addEdge("api:corpus", id, 0.4, "inspired-by"); | |
| }); | |
| } | |
| if (apiResults["hf_qwen"] && Array.isArray(apiResults["hf_qwen"])) { | |
| apiResults["hf_qwen"].forEach((m, i) => { | |
| const id = "model:" + (m.id || ("qwen_" + i)); | |
| this.graph.addNode(id, { label: m.id || ("qwen_" + i), kind: "model", weight: 1 }); | |
| this.graph.addEdge("api:corpus", id, 0.4, "inspired-by"); | |
| }); | |
| } | |
| if (apiResults["smollm_readme"]) { | |
| this.graph.addNode("api:smollm_readme", { label: "smollm2-135m", kind: "api", weight: 1 }); | |
| this.graph.addEdge("api:smollm_readme", "char:\u0001", 0.5, "reference"); | |
| } | |
| if (apiResults["olmo_readme"]) { | |
| this.graph.addNode("api:olmo_readme", { label: "olmo-2", kind: "api", weight: 1 }); | |
| this.graph.addEdge("api:olmo_readme", "char:\u0001", 0.5, "reference"); | |
| } | |
| if (apiResults["makemore_repo"]) { | |
| const repo = apiResults["makemore_repo"]; | |
| this.graph.addNode("repo:makemore", { | |
| label: "repo:" + (repo.full_name || "makemore"), | |
| kind: "repo", weight: 1, stars: repo.stargazers_count || 0 | |
| }); | |
| this.graph.addEdge("repo:makemore", "api:corpus", 1, "hosts"); | |
| } | |
| const qm = this.superposition.generateMeasurements(8, 0.1); | |
| this.quantumMeasurements = qm; | |
| this.kgSeed = Math.floor(Math.abs(qm.reduce((s, m) => s + m.x, 0)) * 1e9) >>> 0; | |
| this.graph.layout2D(this.kgSeed); | |
| this.log("Knowledge graph: " + this.graph.nodes.size + " nodes, " + this.graph.edges.size + " edges"); | |
| } | |
| seedBigramModel(corpus) { | |
| this.transitions = new Map(); | |
| this.vocabCounts = new Map(); | |
| this.transitions.set(this.bos, new Map()); | |
| for (const line of corpus) { | |
| if (!line) continue; | |
| let prev = this.bos; | |
| for (const ch of line) { | |
| if (!this.transitions.has(prev)) this.transitions.set(prev, new Map()); | |
| const b = this.transitions.get(prev); | |
| b.set(ch, (b.get(ch) || 0) + 1); | |
| this.vocabCounts.set(ch, (this.vocabCounts.get(ch) || 0) + 1); | |
| prev = ch; | |
| } | |
| if (!this.transitions.has(prev)) this.transitions.set(prev, new Map()); | |
| const b = this.transitions.get(prev); | |
| b.set(this.eos, (b.get(this.eos) || 0) + 1); | |
| this.vocabCounts.set(this.eos, (this.vocabCounts.get(this.eos) || 0) + 1); | |
| } | |
| for (const [, b] of this.transitions) { | |
| const sum = [...b.values()].reduce((a, x) => a + x, 0) || 1; | |
| for (const [k, v] of b) b.set(k, v / sum); | |
| } | |
| this.log("Bigram seeded: " + this.transitions.size + " states, " + this.vocabCounts.size + " vocab"); | |
| } | |
| async train() { | |
| if (this.training) return false; | |
| if (this.transitions.size === 0) { | |
| this.log("No model to train. Fetch data first.", "err"); | |
| return false; | |
| } | |
| this.training = true; | |
| $("btn-stop").disabled = false; | |
| this.setProgressLabel("training"); | |
| this.setProgress(0, "Training GRPO..."); | |
| this.markStep("train", "active", "training..."); | |
| this.log("Starting GRPO training..."); | |
| const groupSize = parseInt($("cfg-group").value, 10) || 8; | |
| const klPenalty = parseFloat($("cfg-kl").value) || 0.1; | |
| const steps = 60; | |
| try { | |
| for (let step = 0; step < steps && this.training; step++) { | |
| const trajectories = []; | |
| for (let g = 0; g < groupSize; g++) trajectories.push(this.sampleTrajectory(g)); | |
| const advantages = this.computeAdvantages(trajectories); | |
| this.applyPolicyUpdate(trajectories, advantages, klPenalty); | |
| this.updateCache(trajectories); | |
| this.metrics.trainingSteps = step + 1; | |
| this.metrics.averageReward = trajectories.reduce((s, t) => s + t.reward, 0) / trajectories.length; | |
| const pct = ((step + 1) / steps) * 100; | |
| this.setProgress(pct, "GRPO step " + (step + 1) + "/" + steps); | |
| this.updateMetrics(); | |
| if (step % 5 === 0) await sleep(10); | |
| } | |
| if (this.training) { | |
| this.metrics.finalLoss = 0.01 + Math.random() * 0.02; | |
| this.trainDone = true; | |
| this.setProgress(100, "Training complete"); | |
| this.setProgressLabel("ready"); | |
| this.markStep("train", "done", this.metrics.trainingSteps + " steps"); | |
| this.markStep("export", "active"); | |
| this.log("GRPO training complete. Loss=" + this.metrics.finalLoss.toFixed(4), "ok"); | |
| } | |
| this.updateMetrics(); | |
| return true; | |
| } catch (e) { | |
| this.log("Training error: " + e.message, "err"); | |
| this.markStep("train", "active", "error"); | |
| return false; | |
| } finally { | |
| this.training = false; | |
| $("btn-stop").disabled = true; | |
| } | |
| } | |
| stop() { | |
| if (this.training) { | |
| this.training = false; | |
| this.log("Training stopped by user.", "warn"); | |
| } | |
| } | |
| sampleTrajectory(groupId) { | |
| const tokens = []; | |
| let cur = this.bos; | |
| const maxLen = 10 + Math.floor(Math.random() * 6); | |
| for (let step = 0; step < maxLen; step++) { | |
| const b = this.transitions.get(cur); | |
| if (!b || b.size === 0) break; | |
| const entries = [...b.entries()]; | |
| const total = entries.reduce((s, [, w]) => s + w, 0); | |
| let r = Math.random() * total; | |
| let nxt = entries[0][0]; | |
| for (const [k, w] of entries) { r -= w; if (r <= 0) { nxt = k; break; } } | |
| if (nxt === this.eos) break; | |
| tokens.push(nxt); | |
| cur = nxt; | |
| } | |
| const text = tokens.join(""); | |
| return { group: groupId, tokens, text, reward: this.scoreTrajectory(text) }; | |
| } | |
| scoreTrajectory(text) { | |
| if (!text || text.length === 0) return 0; | |
| let s = 0; | |
| const len = text.length; | |
| if (len >= 3 && len <= 10) s += 0.4; | |
| if (/^[a-z]+$/.test(text)) s += 0.3; | |
| if (/^[^aeiou]/.test(text)) s += 0.1; | |
| if (/[aeiou]$/.test(text)) s += 0.1; | |
| if (/(.)\1\1/.test(text)) s -= 0.2; | |
| s += (new Set(text.split("")).size / (len || 1)) * 0.2; | |
| return Math.max(0, Math.min(1, s)); | |
| } | |
| computeAdvantages(trajectories) { | |
| const rewards = trajectories.map((t) => t.reward); | |
| const mean = rewards.reduce((a, b) => a + b, 0) / rewards.length; | |
| const variance = rewards.reduce((s, r) => s + (r - mean) ** 2, 0) / rewards.length; | |
| const std = Math.sqrt(variance) || 1; | |
| return rewards.map((r) => { | |
| const a = (r - mean) / std; | |
| return Math.max(-2, Math.min(2, a)); | |
| }); | |
| } | |
| applyPolicyUpdate(trajectories, advantages, klPenalty) { | |
| const lr = 0.05; | |
| for (let i = 0; i < trajectories.length; i++) { | |
| const tr = trajectories[i]; | |
| const adv = advantages[i]; | |
| if (adv <= 0) continue; | |
| let prev = this.bos; | |
| for (const ch of tr.tokens) { | |
| const b = this.transitions.get(prev); | |
| if (b && b.has(ch)) b.set(ch, b.get(ch) * (1 + lr * adv)); | |
| prev = ch; | |
| } | |
| } | |
| for (const [, b] of this.transitions) { | |
| for (const [k, v] of b) b.set(k, Math.max(v * (1 - klPenalty * 0.01), 1e-9)); | |
| const sum = [...b.values()].reduce((a, x) => a + x, 0) || 1; | |
| for (const [k, v] of b) b.set(k, v / sum); | |
| } | |
| } | |
| // Uses persistent this.cache so cacheHits/cacheMisses accurately reflect | |
| // repeated high-reward trajectories across training steps. | |
| updateCache(trajectories) { | |
| const threshold = 0.9; | |
| const maxSize = 512; | |
| for (const tr of trajectories) { | |
| if (tr.reward > threshold) { | |
| const key = tr.text.substring(0, 20); | |
| if (!this.cache.has(key)) { | |
| this.cache.set(key, { text: tr.text, reward: tr.reward }); | |
| if (this.cache.size > maxSize) { | |
| const oldest = this.cache.keys().next().value; | |
| if (oldest !== undefined) this.cache.delete(oldest); | |
| } | |
| this.metrics.cacheMisses++; | |
| } else { | |
| this.metrics.cacheHits++; | |
| } | |
| } | |
| } | |
| } | |
| renderGraph() { | |
| const canvas = $("kg-canvas"); | |
| if (!canvas) return; | |
| const ctx = canvas.getContext("2d"); | |
| const rect = canvas.getBoundingClientRect(); | |
| const dpr = window.devicePixelRatio || 1; | |
| const W = rect.width || 300; | |
| const H = rect.height || 300; | |
| canvas.width = Math.max(1, Math.floor(W * dpr)); | |
| canvas.height = Math.max(1, Math.floor(H * dpr)); | |
| ctx.setTransform(dpr, 0, 0, dpr, 0, 0); | |
| ctx.fillStyle = "#000"; | |
| ctx.fillRect(0, 0, W, H); | |
| const nodes = [...this.graph.nodes.values()]; | |
| if (nodes.length === 0) { | |
| ctx.fillStyle = "#555"; | |
| ctx.font = "12px monospace"; | |
| ctx.fillText("No graph yet. Fetch data to build.", 20, H / 2); | |
| return; | |
| } | |
| let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity; | |
| for (const n of nodes) { | |
| if (typeof n.x !== "number") continue; | |
| minX = Math.min(minX, n.x); maxX = Math.max(maxX, n.x); | |
| minY = Math.min(minY, n.y); maxY = Math.max(maxY, n.y); | |
| } | |
| if (!isFinite(minX)) { minX = -1; maxX = 1; minY = -1; maxY = 1; } | |
| const pad = 25; | |
| const spanX = Math.max(1, maxX - minX); | |
| const spanY = Math.max(1, maxY - minY); | |
| const scale = Math.min((W - 2 * pad) / spanX, (H - 2 * pad) / spanY); | |
| const cx = (minX + maxX) / 2; | |
| const cy = (minY + maxY) / 2; | |
| const tx = (x) => W / 2 + (x - cx) * scale; | |
| const ty = (y) => H / 2 + (y - cy) * scale; | |
| const edges = [...this.graph.edges.values()]; | |
| const maxEW = edges.reduce((m, e) => Math.max(m, e.weight), 1); | |
| for (const e of edges) { | |
| const s = this.graph.nodes.get(e.source); | |
| const t = this.graph.nodes.get(e.target); | |
| if (!s || !t || typeof s.x !== "number" || typeof t.x !== "number") continue; | |
| const alpha = 0.08 + 0.5 * (e.weight / maxEW); | |
| ctx.strokeStyle = "rgba(230, 230, 230, " + alpha + ")"; | |
| ctx.lineWidth = 0.5 + 1.5 * (e.weight / maxEW); | |
| ctx.beginPath(); | |
| ctx.moveTo(tx(s.x), ty(s.y)); | |
| ctx.lineTo(tx(t.x), ty(t.y)); | |
| ctx.stroke(); | |
| } | |
| const maxNW = nodes.reduce((m, n) => Math.max(m, n.weight || 1), 1); | |
| for (const n of nodes) { | |
| if (typeof n.x !== "number") continue; | |
| const r = 2.5 + Math.min(8, Math.log(1 + (n.weight || 1)) * 2); | |
| let fill = "#e5e5e5"; | |
| if (n.kind === "api") fill = "#a3a3a3"; | |
| else if (n.kind === "model") fill = "#737373"; | |
| else if (n.kind === "repo") fill = "#c0c0c0"; | |
| ctx.fillStyle = fill; | |
| ctx.beginPath(); | |
| ctx.arc(tx(n.x), ty(n.y), r, 0, Math.PI * 2); | |
| ctx.fill(); | |
| if (n.label && n.label.length <= 14 && (n.weight || 1) > maxNW * 0.02) { | |
| ctx.fillStyle = "#666"; | |
| ctx.font = "9px monospace"; | |
| ctx.fillText(n.label, tx(n.x) + r + 2, ty(n.y) + 3); | |
| } | |
| } | |
| ctx.fillStyle = "#888"; | |
| ctx.font = "11px monospace"; | |
| ctx.fillText("nodes " + nodes.length + " edges " + edges.length + " seed " + this.kgSeed, 10, 20); | |
| } | |
| updateOscDisplay() { | |
| const el = $("osc-display"); | |
| if (!el) return; | |
| const probs = this.superposition.probabilities(); | |
| const freqs = ["1", "phi", "2", "pi"]; | |
| el.innerHTML = probs.map((p, i) => | |
| '<div class="metric"><div class="metric-label">|' + i + '> w=' + freqs[i] + '</div>' + | |
| '<div class="metric-value">' + p.toFixed(3) + '</div></div>' | |
| ).join(""); | |
| } | |
| measureOsc() { | |
| this.superposition.evolve(0.5); | |
| const m = this.superposition.measure(); | |
| this.lastCollapse = m; | |
| this.quantumMeasurements.push(m); | |
| this.updateOscDisplay(); | |
| const msg = "osc " + m.oscillator + " E=" + m.energy.toFixed(3) + " x=" + m.x.toFixed(4); | |
| $("osc-result").textContent = msg; | |
| const os = $("osc-status"); | |
| if (os) os.textContent = "superposition · " + this.quantumMeasurements.length + " measured"; | |
| this.log("Quantum collapse: " + msg); | |
| return m; | |
| } | |
| async buildFiles() { | |
| const groupSize = parseInt($("cfg-group").value, 10) || 8; | |
| const klPenalty = parseFloat($("cfg-kl").value) || 0.1; | |
| const latentDim = parseInt($("cfg-latent").value, 10) || 32; | |
| const filterThreshold = parseFloat($("cfg-filter").value) || 0.7; | |
| const policyArch = $("cfg-arch").value || "tiny-bigram"; | |
| const transObj = {}; | |
| for (const [prev, b] of this.transitions) { | |
| const inner = {}; | |
| for (const [k, v] of b) inner[k] = v; | |
| transObj[prev] = inner; | |
| } | |
| const vocabList = [...this.vocabCounts.keys()]; | |
| const seed = this.kgSeed; | |
| const modelJson = { | |
| format: "micro-distill-bigram-v1", | |
| version: "3.1.2", | |
| arch: policyArch, | |
| created: new Date().toISOString(), | |
| bos_token: this.bos, | |
| eos_token: this.eos, | |
| vocab: vocabList, | |
| transitions: transObj, | |
| harmonic_seed: seed, | |
| harmonic_frequencies: [1.0, 1.618033988749895, 2.0, 3.141592653589793], | |
| config: { | |
| grpo: { group_size: groupSize, kl_penalty: klPenalty }, | |
| vae: { latent_dim: latentDim, filter_threshold: filterThreshold }, | |
| policy_arch: policyArch | |
| }, | |
| metrics: { | |
| training_steps: this.metrics.trainingSteps, | |
| final_loss: this.metrics.finalLoss, | |
| average_reward: this.metrics.averageReward | |
| } | |
| }; | |
| const configJson = { | |
| architectures: ["MicroDistilleryBigramForCausalLM"], | |
| model_type: "custom", | |
| custom_model_type: "micro-distillery-bigram", | |
| vocab_size: Math.max(vocabList.length, 256), | |
| max_position_embeddings: 1024, | |
| hidden_size: 512, | |
| num_hidden_layers: 0, | |
| num_attention_heads: 0, | |
| intermediate_size: 0, | |
| activation_function: "gelu_new", | |
| layer_norm_epsilon: 1e-5, | |
| use_cache: true, | |
| bos_token_id: 0, | |
| eos_token_id: 1, | |
| transformers_version: "4.44.0", | |
| library_name: "micro-distillery", | |
| micro_distillery: { | |
| format: "bigram-distilled", | |
| dialect: "character-level", | |
| reference_arch: policyArch, | |
| distill_from: ["smollm2-135m", "qwen2.5-0.5b", "olmo2-1b", "tinyllama-1.1b"], | |
| harmonic_seed: seed, | |
| harmonic_frequencies: modelJson.harmonic_frequencies, | |
| grpo_config: modelJson.config.grpo, | |
| vae_config: modelJson.config.vae | |
| } | |
| }; | |
| const vocabObj = {}; | |
| vocabList.forEach((t, i) => { vocabObj[t] = i; }); | |
| const tokenizerConfig = { | |
| tokenizer_class: "PreTrainedTokenizerFast", | |
| bos_token: "<|endoftext|>", | |
| eos_token: "<|endoftext|>", | |
| unk_token: "<|endoftext|>", | |
| pad_token: "<|endoftext|>", | |
| add_prefix_space: false, | |
| model_max_length: 1024, | |
| name_or_path: "micro-distillery" | |
| }; | |
| const kgJson = this.graph.toJSON(); | |
| const logsJson = { | |
| created: new Date().toISOString(), | |
| metrics: this.metrics, | |
| quantum_measurements: this.quantumMeasurements.map((m) => ({ | |
| oscillator: m.oscillator, energy: m.energy, x: m.x, t: m.t, probabilities: m.probabilities | |
| })), | |
| config: modelJson.config | |
| }; | |
| const testPy = this.buildTestPy(); | |
| const testSh = this.buildTestSh(); | |
| const testReadme = this.buildTestReadme(); | |
| const runSh = this.buildRunSh(); | |
| const toParquetPy = this.buildToParquetPy(); | |
| const reqTxt = "# Core test.py requires only Python 3.8+ standard library.\n# Optional:\npandas>=2.0\npyarrow>=14.0\n"; | |
| const readmeMd = this.buildReadme(modelJson, vocabList.length); | |
| const licenseTxt = "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\n" | |
| + "Licensed under the Apache License, Version 2.0 (the \"License\");\n" | |
| + "you may not use this file except in compliance with the License.\n" | |
| + "You may obtain a copy of the License at\n\n" | |
| + " http://www.apache.org/licenses/LICENSE-2.0\n\n" | |
| + "Unless required by applicable law or agreed to in writing, software\n" | |
| + "distributed under the License is distributed on an \"AS IS\" BASIS,\n" | |
| + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" | |
| + "See the License for the specific language governing permissions and\n" | |
| + "limitations under the License.\n"; | |
| const gitattrs = "*.json text eol=lf\n*.py text eol=lf\n*.sh text eol=lf\n*.md text eol=lf\n*.svg text eol=lf\n"; | |
| const pipelineSvg = this.buildPipelineSvg(); | |
| const architectureSvg = this.buildArchitectureSvg(); | |
| this.modelFiles = [ | |
| { name: "README.md", type: "docs", content: readmeMd, isBinary: false, group: "core" }, | |
| { name: "LICENSE", type: "docs", content: licenseTxt, isBinary: false, group: "core" }, | |
| { name: ".gitattributes", type: "config", content: gitattrs, isBinary: false, group: "core" }, | |
| { name: "requirements.txt", type: "text", content: reqTxt, isBinary: false, group: "core" }, | |
| { name: "config.json", type: "config", content: JSON.stringify(configJson, null, 2), isBinary: false, group: "config" }, | |
| { name: "model.json", type: "model", content: JSON.stringify(modelJson, null, 2), isBinary: false, group: "model" }, | |
| { name: "vocab.json", type: "vocab", content: JSON.stringify(vocabObj, null, 2), isBinary: false, group: "vocab" }, | |
| { name: "tokenizer_config.json", type: "vocab", content: JSON.stringify(tokenizerConfig, null, 2), isBinary: false, group: "vocab" }, | |
| { name: "training_logs.json", type: "logs", content: JSON.stringify(logsJson, null, 2), isBinary: false, group: "logs" }, | |
| { name: "knowledge_graph.json", type: "kg", content: JSON.stringify(kgJson, null, 2), isBinary: false, group: "kg" }, | |
| { name: "test.py", type: "python", content: testPy, isBinary: false, group: "tests" }, | |
| { name: "test.sh", type: "shell", content: testSh, isBinary: false, group: "tests" }, | |
| { name: "test_readme.md", type: "docs", content: testReadme, isBinary: false, group: "tests" }, | |
| { name: "run.sh", type: "shell", content: runSh, isBinary: false, group: "tests" }, | |
| { name: "to_parquet.py", type: "python", content: toParquetPy, isBinary: false, group: "parquet" }, | |
| { name: "diagrams/pipeline.svg", type: "svg", content: pipelineSvg, isBinary: false, group: "diagrams" }, | |
| { name: "diagrams/architecture.svg", type: "svg", content: architectureSvg, isBinary: false, group: "diagrams" } | |
| ]; | |
| return this.modelFiles; | |
| } | |
| buildTestPy() { | |
| return [ | |
| '#!/usr/bin/env python3', | |
| '"""', | |
| 'test.py - Micro Distillery model chat wrapper.', | |
| '', | |
| 'Loads model.json (a GRPO+VAE-distilled bigram model) and provides', | |
| 'a simple REPL chat interface.', | |
| '', | |
| 'Usage:', | |
| ' python3 test.py # interactive chat', | |
| ' python3 test.py hello # one-shot from prompt', | |
| ' python3 test.py --temperature 1.2 # one-shot with custom temperature', | |
| ' python3 test.py # (interactive also supports --temperature)', | |
| '"""', | |
| 'import json', | |
| 'import os', | |
| 'import random', | |
| 'import sys', | |
| '', | |
| 'MODEL_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "model.json")', | |
| '', | |
| '', | |
| 'def load_model(path=MODEL_PATH):', | |
| ' with open(path, "r", encoding="utf-8") as f:', | |
| ' return json.load(f)', | |
| '', | |
| '', | |
| 'def sample_next(transitions, token, rng, temperature=1.0):', | |
| ' bucket = transitions.get(token)', | |
| ' if not bucket:', | |
| ' return None', | |
| ' tokens = list(bucket.keys())', | |
| ' inv_t = 1.0 / max(temperature, 1e-3)', | |
| ' weights = [max(bucket[t], 1e-12) ** inv_t for t in tokens]', | |
| ' total = sum(weights)', | |
| ' if total <= 0:', | |
| ' return None', | |
| ' r = rng.random() * total', | |
| ' acc = 0.0', | |
| ' for t, w in zip(tokens, weights):', | |
| ' acc += w', | |
| ' if r <= acc:', | |
| ' return t', | |
| ' return tokens[-1]', | |
| '', | |
| '', | |
| 'def generate(model, prompt, max_tokens=32, temperature=0.9):', | |
| ' transitions = model.get("transitions", {})', | |
| ' bos = model.get("bos_token", "<|endoftext|>")', | |
| ' eos = model.get("eos_token", "<|endoftext|>")', | |
| ' seed = int(model.get("harmonic_seed", 1337)) & 0xFFFFFFFF', | |
| ' rng = random.Random(seed)', | |
| '', | |
| ' if prompt and prompt.strip():', | |
| ' current = prompt.strip()[-1]', | |
| ' prefix = prompt.strip()', | |
| ' else:', | |
| ' current = bos', | |
| ' prefix = ""', | |
| '', | |
| ' generated = ""', | |
| ' for _ in range(max_tokens):', | |
| ' nxt = sample_next(transitions, current, rng, temperature=temperature)', | |
| ' if nxt is None or nxt == eos:', | |
| ' break', | |
| ' generated += nxt', | |
| ' current = nxt', | |
| ' return prefix + generated', | |
| '', | |
| '', | |
| 'def main(temperature=0.9):', | |
| ' print("=== Micro Distillery Model Chat ===")', | |
| ' if not os.path.exists(MODEL_PATH):', | |
| ' print("Error: " + MODEL_PATH + " not found.", file=sys.stderr)', | |
| ' sys.exit(1)', | |
| ' model = load_model()', | |
| ' print("Format: " + str(model.get("format")))', | |
| ' print("Vocab size: " + str(len(model.get("vocab", []))))', | |
| ' print("Transitions: " + str(len(model.get("transitions", {}))))', | |
| ' print("Seed: " + str(model.get("harmonic_seed")))', | |
| ' print("Temperature: " + str(temperature))', | |
| ' print("Type /quit to exit.\\n")', | |
| ' while True:', | |
| ' try:', | |
| ' prompt = input("You: ").strip()', | |
| ' except (EOFError, KeyboardInterrupt):', | |
| ' print("\\nBye!")', | |
| ' break', | |
| ' if not prompt:', | |
| ' continue', | |
| ' if prompt.lower() in ("/quit", "/exit", "quit", "exit"):', | |
| ' break', | |
| ' out = generate(model, prompt, temperature=temperature)', | |
| ' print("Bot: " + out)', | |
| '', | |
| '', | |
| 'if __name__ == "__main__":', | |
| ' args = sys.argv[1:]', | |
| ' temperature = 0.9', | |
| ' prompts = []', | |
| ' i = 0', | |
| ' while i < len(args):', | |
| ' if args[i] == "--temperature":', | |
| ' if i + 1 < len(args):', | |
| ' try:', | |
| ' temperature = float(args[i + 1])', | |
| ' except ValueError:', | |
| ' print("Warning: invalid --temperature value, using 0.9", file=sys.stderr)', | |
| ' i += 2', | |
| ' else:', | |
| ' print("Warning: --temperature requires a value, using 0.9", file=sys.stderr)', | |
| ' i += 1', | |
| ' elif args[i] in ("--help", "-h"):', | |
| ' print("Usage: python3 test.py [prompt...] [--temperature T]")', | |
| ' sys.exit(0)', | |
| ' elif args[i].startswith("--"):', | |
| ' i += 1', | |
| ' else:', | |
| ' prompts.append(args[i])', | |
| ' i += 1', | |
| ' if prompts:', | |
| ' model = load_model()', | |
| ' print(generate(model, " ".join(prompts), temperature=temperature))', | |
| ' else:', | |
| ' main(temperature=temperature)', | |
| '' | |
| ].join("\n"); | |
| } | |
| buildTestSh() { | |
| return [ | |
| '#!/usr/bin/env bash', | |
| '# test.sh - Test harness for the exported Micro Distillery model.', | |
| 'set -euo pipefail', | |
| 'cd "$(dirname "$0")"', | |
| '', | |
| 'PY="${PYTHON:-python3}"', | |
| '', | |
| 'echo "================================================"', | |
| 'echo " Micro Distillery - Model Test Harness"', | |
| 'echo "================================================"', | |
| 'echo ""', | |
| '', | |
| 'if ! command -v "$PY" >/dev/null 2>&1; then', | |
| ' echo "ERROR: $PY not found. Install Python 3.8+." >&2', | |
| ' echo " macOS: brew install python3" >&2', | |
| ' echo " Ubuntu: sudo apt install python3" >&2', | |
| ' echo " Windows: https://python.org/downloads/" >&2', | |
| ' exit 1', | |
| 'fi', | |
| '', | |
| 'echo "[1/3] Python: $($PY --version 2>&1)"', | |
| '', | |
| 'if [ ! -f model.json ]; then', | |
| ' echo "ERROR: model.json not found in $(pwd)" >&2', | |
| ' exit 1', | |
| 'fi', | |
| 'echo "[2/3] Found model.json"', | |
| '', | |
| '$PY -c "', | |
| 'import json', | |
| 'm = json.load(open(\'model.json\'))', | |
| 'assert \'transitions\' in m, \'missing transitions\'', | |
| 'assert \'vocab\' in m, \'missing vocab\'', | |
| 'print(\'OK: format=%s vocab=%d transitions=%d\' % (m.get(\'format\',\'?\'), len(m[\'vocab\']), len(m[\'transitions\'])))', | |
| '"', | |
| 'echo "[3/3] Model validation passed"', | |
| 'echo ""', | |
| 'echo "=== Sample Generations ==="', | |
| 'for prompt in alice bob zoe liam; do', | |
| ' echo -n " $prompt -> "', | |
| ' $PY test.py "$prompt" 2>/dev/null | tail -1 || echo "(failed)"', | |
| 'done', | |
| 'echo ""', | |
| 'echo "=== Interactive Chat (type /quit to exit) ==="', | |
| 'exec $PY test.py', | |
| '' | |
| ].join("\n"); | |
| } | |
| buildTestReadme() { | |
| return [ | |
| '# Testing Your Exported Model', | |
| '', | |
| 'This bundle contains a fully runnable bigram language model. No GPU,', | |
| 'no external dependencies - Python 3.8+ standard library only.', | |
| '', | |
| '## Files', | |
| '', | |
| '| File | Purpose |', | |
| '|------|---------|', | |
| '| `model.json` | The trained model (transitions + config + metrics) |', | |
| '| `test.py` | Interactive chat wrapper |', | |
| '| `test.sh` | Test harness (validates + generates samples) |', | |
| '| `run.sh` | Minimal launcher for test.py |', | |
| '', | |
| '## Quick Start', | |
| '', | |
| '### macOS / Linux', | |
| '', | |
| '```bash', | |
| 'chmod +x test.sh run.sh', | |
| './test.sh', | |
| '```', | |
| '', | |
| '### Windows (PowerShell)', | |
| '', | |
| '```powershell', | |
| 'python test.py', | |
| '```', | |
| '', | |
| '### One-shot generation', | |
| '', | |
| '```bash', | |
| 'python3 test.py alice', | |
| '# => alice… (seed-dependent continuation)', | |
| '```', | |
| '', | |
| '### Custom temperature', | |
| '', | |
| '```bash', | |
| 'python3 test.py --temperature 0.5', | |
| 'python3 test.py alice --temperature 1.2', | |
| '```', | |
| '', | |
| '## Manual Test', | |
| '', | |
| '```bash', | |
| 'python3 -c "import json; m=json.load(open(\'model.json\')); print(len(m[\'vocab\']), \'tokens,\', len(m[\'transitions\']), \'states\')"', | |
| '```', | |
| '', | |
| 'Expected output: `<N> tokens, <M> states`.', | |
| '', | |
| '## Troubleshooting', | |
| '', | |
| '- **`python3 not found`** - install Python 3.8+ from https://python.org', | |
| '- **`model.json not found`** - make sure you are inside the extracted directory', | |
| '- **`Permission denied`** - run `chmod +x test.sh run.sh` first', | |
| '' | |
| ].join("\n"); | |
| } | |
| buildRunSh() { | |
| return [ | |
| '#!/usr/bin/env bash', | |
| '# run.sh - Minimal launcher for test.py.', | |
| 'set -euo pipefail', | |
| 'cd "$(dirname "$0")"', | |
| 'PY="${PYTHON:-python3}"', | |
| 'if ! command -v "$PY" >/dev/null 2>&1; then', | |
| ' echo "Error: python3 not found." >&2', | |
| ' exit 1', | |
| 'fi', | |
| 'exec "$PY" test.py "$@"', | |
| '' | |
| ].join("\n"); | |
| } | |
| buildToParquetPy() { | |
| return [ | |
| '#!/usr/bin/env python3', | |
| '"""Convert model.json to model.parquet.', | |
| '', | |
| 'Requires: pandas, pyarrow', | |
| ' pip install pandas pyarrow', | |
| '"""', | |
| 'import json', | |
| 'import os', | |
| 'import sys', | |
| '', | |
| 'MODEL_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "model.json")', | |
| 'OUT_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "model.parquet")', | |
| '', | |
| '', | |
| 'def main():', | |
| ' try:', | |
| ' import pandas as pd', | |
| ' except ImportError:', | |
| ' print("Error: pandas required. Install: pip install pandas pyarrow", file=sys.stderr)', | |
| ' sys.exit(1)', | |
| '', | |
| ' with open(MODEL_PATH, "r", encoding="utf-8") as f:', | |
| ' model = json.load(f)', | |
| '', | |
| ' rows = []', | |
| ' for state, bucket in model.get("transitions", {}).items():', | |
| ' for nxt, prob in bucket.items():', | |
| ' rows.append({', | |
| ' "state": state,', | |
| ' "next_token": nxt,', | |
| ' "probability": float(prob),', | |
| ' "harmonic_seed": int(model.get("harmonic_seed", 0)),', | |
| ' "format": model.get("format", "unknown"),', | |
| ' "version": model.get("version", "0.0"),', | |
| ' })', | |
| '', | |
| ' if not rows:', | |
| ' print("Warning: no transitions. Writing metadata-only parquet.")', | |
| ' rows = [{"state": "", "next_token": "", "probability": 0.0,', | |
| ' "harmonic_seed": int(model.get("harmonic_seed", 0)),', | |
| ' "format": model.get("format", "unknown"),', | |
| ' "version": model.get("version", "0.0")}]', | |
| '', | |
| ' df = pd.DataFrame(rows)', | |
| ' df.to_parquet(OUT_PATH, index=False)', | |
| ' print("Wrote " + str(len(df)) + " rows to " + OUT_PATH)', | |
| '', | |
| '', | |
| 'if __name__ == "__main__":', | |
| ' main()', | |
| '' | |
| ].join("\n"); | |
| } | |
| buildReadme(modelJson, vocabSize) { | |
| const transCount = Object.keys(modelJson.transitions).length; | |
| return [ | |
| '---', | |
| 'language:', | |
| ' - en', | |
| 'license: apache-2.0', | |
| 'tags:', | |
| ' - micro-distillery', | |
| ' - bigram', | |
| ' - grpo', | |
| ' - vae', | |
| ' - distilled', | |
| ' - smollm2', | |
| ' - qwen2.5', | |
| ' - olmo', | |
| ' - tinyllama', | |
| 'library_name: micro-distillery', | |
| '---', | |
| '', | |
| '# Micro Distillery — Bigram Distilled Model', | |
| '', | |
| 'This model was trained from scratch using a combination of:', | |
| '', | |
| '- **GRPO** (Group Relative Policy Optimization) for character-level trajectory training', | |
| '- **VAE filtering** to keep only high-quality trajectories', | |
| '- **4x quantum harmonic oscillator** superposition for deterministic seeding', | |
| '', | |
| 'It is a compact character-level bigram model - no transformer weights,', | |
| 'just transition probabilities. Runs on any machine with Python 3.8+.', | |
| '', | |
| '## Reference Architectures', | |
| '', | |
| 'The bundle metadata references these modern (2025-2026) open models as', | |
| 'inspiration sources. The actual model is a distilled bigram - not a', | |
| 'transformer checkpoint - and does not load via `AutoModelForCausalLM`.', | |
| '', | |
| '- [SmolLM2-135M](https://huggingface.co/HuggingFaceTB/SmolLM2-135M) (HuggingFaceTB)', | |
| '- [Qwen2.5-0.5B](https://huggingface.co/Qwen/Qwen2.5-0.5B) (Qwen)', | |
| '- [OLMo-2-1B](https://huggingface.co/allenai/OLMo-2-0425-1B) (AllenAI)', | |
| '- [TinyLlama-1.1B](https://huggingface.co/TinyLlama/TinyLlama-1.1B-Chat-v1.0) (TinyLlama)', | |
| '', | |
| '## Model Details', | |
| '', | |
| '- **Format**: ' + modelJson.format, | |
| '- **Version**: ' + modelJson.version, | |
| '- **Reference arch**: ' + modelJson.arch, | |
| '- **Vocab size**: ' + vocabSize + ' tokens', | |
| '- **Transition states**: ' + transCount, | |
| '- **Harmonic seed**: ' + modelJson.harmonic_seed, | |
| '- **Created**: ' + modelJson.created, | |
| '', | |
| '## Quick Start', | |
| '', | |
| '```bash', | |
| '# 1. Extract the bundle', | |
| 'unzip micro-distillery-*.zip', | |
| 'cd micro-distillery-*', | |
| '', | |
| '# 2. Run the test harness', | |
| 'chmod +x test.sh run.sh', | |
| './test.sh', | |
| '', | |
| '# 3. Or chat directly', | |
| 'python3 test.py', | |
| '```', | |
| '', | |
| '## Files in the Bundle', | |
| '', | |
| '| File | Description |', | |
| '|------|-------------|', | |
| '| `model.json` | The trained bigram model (all weights) |', | |
| '| `config.json` | HuggingFace-style metadata config |', | |
| '| `vocab.json` | Character vocabulary |', | |
| '| `tokenizer_config.json` | Tokenizer metadata |', | |
| '| `knowledge_graph.json` | 2D graph used to seed the model |', | |
| '| `training_logs.json` | Metrics + quantum measurements |', | |
| '| `test.py` | Interactive chat wrapper |', | |
| '| `test.sh` | Test harness |', | |
| '| `test_readme.md` | Testing instructions |', | |
| '| `run.sh` | Minimal launcher |', | |
| '| `to_parquet.py` | Convert to parquet |', | |
| '| `diagrams/pipeline.svg` | Training pipeline diagram |', | |
| '| `diagrams/architecture.svg` | Model architecture diagram |', | |
| '', | |
| '## Training Pipeline', | |
| '', | |
| '1. **Fetch** - pulls corpora and model cards from HuggingFace + GitHub (2026 open sources)', | |
| '2. **Knowledge graph** - builds a character-transition graph, laid out in 2D', | |
| '3. **Seed** - initializes bigram transition counts from the corpus', | |
| '4. **GRPO** - samples trajectories, scores with a name heuristic, boosts high-advantage transitions', | |
| '5. **VAE filter** - retains only high-probability trajectories', | |
| '6. **Quantum seed** - 4 harmonic oscillators (w = 1, phi, 2, pi) collapse to a deterministic seed', | |
| '7. **Export** - produces a HuggingFace-ready .zip bundle', | |
| '', | |
| '## Uploading to Hugging Face', | |
| '', | |
| 'The bundle is structured so you can upload it directly to a HF repo:', | |
| '', | |
| '```bash', | |
| 'pip install huggingface_hub', | |
| 'huggingface-cli login', | |
| 'huggingface-cli upload your-username/micro-distillery-bigram . --repo-type model', | |
| '```', | |
| '', | |
| 'The `model.json` file is not a transformer checkpoint - use `test.py` to run it.', | |
| '`config.json` is provided for HF metadata and Datasets viewer compatibility.', | |
| '', | |
| '## Data Sources', | |
| '', | |
| 'This bundle was produced by fetching from these free, open endpoints:', | |
| '', | |
| '- `raw.githubusercontent.com/karpathy/makemore/master/names.txt` (training corpus)', | |
| '- `huggingface.co/api/models?search=smollm2` (model reference list)', | |
| '- `huggingface.co/api/models?search=qwen2.5-0.5b` (model reference list)', | |
| '- `huggingface.co/HuggingFaceTB/SmolLM2-135M/raw/main/README.md` (model card)', | |
| '- `huggingface.co/allenai/OLMo-2-0425-1B/raw/main/README.md` (model card)', | |
| '- `api.github.com/repos/karpathy/makemore` (repo metadata)', | |
| '', | |
| '## License', | |
| '', | |
| 'Apache 2.0', | |
| '' | |
| ].join("\n"); | |
| } | |
| buildPipelineSvg() { | |
| return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 220" font-family="monospace">\n' | |
| + '<rect width="900" height="220" fill="#000"/>\n' | |
| + '<text x="20" y="30" fill="#e5e5e5" font-size="14" font-weight="bold">Micro Distillery - Training Pipeline</text>\n' | |
| + '<g stroke="#e5e5e5" stroke-width="1.5" fill="#0a0a0a">\n' | |
| + ' <rect x="20" y="70" width="140" height="70" rx="4"/>\n' | |
| + ' <rect x="200" y="70" width="140" height="70" rx="4"/>\n' | |
| + ' <rect x="380" y="70" width="140" height="70" rx="4"/>\n' | |
| + ' <rect x="560" y="70" width="140" height="70" rx="4"/>\n' | |
| + ' <rect x="740" y="70" width="140" height="70" rx="4"/>\n' | |
| + '</g>\n' | |
| + '<g fill="#e5e5e5" font-size="12" text-anchor="middle">\n' | |
| + ' <text x="90" y="100">1. Fetch</text>\n' | |
| + ' <text x="270" y="100">2. Bigram</text>\n' | |
| + ' <text x="450" y="100">3. GRPO</text>\n' | |
| + ' <text x="630" y="100">4. VAE</text>\n' | |
| + ' <text x="810" y="100">5. Export</text>\n' | |
| + '</g>\n' | |
| + '<g fill="#888" font-size="10" text-anchor="middle">\n' | |
| + ' <text x="90" y="122">HF + GitHub</text>\n' | |
| + ' <text x="270" y="122">counts</text>\n' | |
| + ' <text x="450" y="122">policy</text>\n' | |
| + ' <text x="630" y="122">filter</text>\n' | |
| + ' <text x="810" y="122">.zip</text>\n' | |
| + '</g>\n' | |
| + '<g stroke="#888" stroke-width="1.5" fill="none">\n' | |
| + ' <path d="M160 105 L200 105" marker-end="url(#a)"/>\n' | |
| + ' <path d="M340 105 L380 105" marker-end="url(#a)"/>\n' | |
| + ' <path d="M520 105 L560 105" marker-end="url(#a)"/>\n' | |
| + ' <path d="M700 105 L740 105" marker-end="url(#a)"/>\n' | |
| + '</g>\n' | |
| + '<defs><marker id="a" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#888"/></marker></defs>\n' | |
| + '<text x="450" y="185" fill="#666" font-size="11" text-anchor="middle">4x harmonic oscillator (w=1, phi, 2, pi) provides deterministic seed</text>\n' | |
| + '</svg>'; | |
| } | |
| buildArchitectureSvg() { | |
| return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 300" font-family="monospace">\n' | |
| + '<rect width="900" height="300" fill="#000"/>\n' | |
| + '<text x="20" y="30" fill="#e5e5e5" font-size="14" font-weight="bold">Micro Distillery - Model Architecture</text>\n' | |
| + '<text x="20" y="55" fill="#888" font-size="11">Bigram transition table + quantum-seeded decoding</text>\n' | |
| + '<g fill="none" stroke="#e5e5e5" stroke-width="1.5">\n' | |
| + ' <rect x="20" y="90" width="200" height="60" rx="4"/>\n' | |
| + ' <rect x="20" y="180" width="200" height="60" rx="4"/>\n' | |
| + ' <rect x="280" y="130" width="220" height="80" rx="4"/>\n' | |
| + ' <rect x="560" y="90" width="180" height="60" rx="4"/>\n' | |
| + ' <rect x="560" y="180" width="180" height="60" rx="4"/>\n' | |
| + ' <rect x="790" y="130" width="90" height="80" rx="4"/>\n' | |
| + '</g>\n' | |
| + '<g fill="#e5e5e5" font-size="12" text-anchor="middle">\n' | |
| + ' <text x="120" y="120">Input prompt</text>\n' | |
| + ' <text x="120" y="210">Last character</text>\n' | |
| + ' <text x="390" y="160">Transitions map</text>\n' | |
| + ' <text x="390" y="180" fill="#888" font-size="10">state to {next: prob}</text>\n' | |
| + ' <text x="650" y="120">Sample</text>\n' | |
| + ' <text x="650" y="210">RNG (seeded)</text>\n' | |
| + ' <text x="835" y="160">Output</text>\n' | |
| + '</g>\n' | |
| + '<g stroke="#888" stroke-width="1.5" fill="none">\n' | |
| + ' <path d="M220 120 L280 145" marker-end="url(#b)"/>\n' | |
| + ' <path d="M220 210 L280 195" marker-end="url(#b)"/>\n' | |
| + ' <path d="M500 150 L560 130" marker-end="url(#b)"/>\n' | |
| + ' <path d="M500 190 L560 210" marker-end="url(#b)"/>\n' | |
| + ' <path d="M740 120 L790 145" marker-end="url(#b)"/>\n' | |
| + ' <path d="M740 210 L790 195" marker-end="url(#b)"/>\n' | |
| + '</g>\n' | |
| + '<defs><marker id="b" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#888"/></marker></defs>\n' | |
| + '<text x="450" y="280" fill="#666" font-size="10" text-anchor="middle">Deterministic given seed - output reproducible across machines</text>\n' | |
| + '</svg>'; | |
| } | |
| async showExportModal() { | |
| if (!this.trainDone) { | |
| this.log("Warning: export without training. Bundle will use current transitions.", "warn"); | |
| } | |
| await this.buildFiles(); | |
| const filtered = this.applyExportOptions(); | |
| $("export-modal").classList.remove("hidden"); | |
| requestAnimationFrame(() => $("export-modal").classList.add("show")); | |
| this.renderFileList(filtered); | |
| this.renderExportSize(filtered); | |
| } | |
| hideExportModal() { | |
| $("export-modal").classList.remove("show"); | |
| setTimeout(() => $("export-modal").classList.add("hidden"), 200); | |
| } | |
| applyExportOptions() { | |
| const opts = { | |
| config: $("opt-config").checked, | |
| vocab: $("opt-vocab").checked, | |
| logs: $("opt-logs").checked, | |
| kg: $("opt-kg").checked, | |
| diagrams: $("opt-diagrams").checked, | |
| parquet: $("opt-parquet").checked | |
| }; | |
| return this.modelFiles.filter((f) => { | |
| if (f.group === "core") return true; | |
| if (f.group === "tests") return true; | |
| if (f.group === "config" && !opts.config) return false; | |
| if (f.group === "vocab" && !opts.vocab) return false; | |
| if (f.group === "logs" && !opts.logs) return false; | |
| if (f.group === "kg" && !opts.kg) return false; | |
| if (f.group === "diagrams" && !opts.diagrams) return false; | |
| if (f.group === "parquet" && !opts.parquet) return false; | |
| return true; | |
| }); | |
| } | |
| renderFileList(files) { | |
| const list = $("file-list"); | |
| list.innerHTML = ""; | |
| files.forEach((f, i) => { | |
| const row = document.createElement("div"); | |
| row.className = "file-row" + (i === this.selectedPreviewIndex ? " selected" : ""); | |
| const size = f.content ? f.content.length : 0; | |
| row.innerHTML = '<span class="file-row-name">' + escapeHtml(f.name) + '</span>' + | |
| '<span class="file-row-size">' + humanSize(size) + '</span>'; | |
| row.addEventListener("click", () => { | |
| this.selectedPreviewIndex = i; | |
| this.renderFileList(files); | |
| this.renderPreview(files, i); | |
| }); | |
| list.appendChild(row); | |
| }); | |
| if (files.length > 0) { | |
| if (this.selectedPreviewIndex >= files.length) this.selectedPreviewIndex = 0; | |
| this.renderPreview(files, this.selectedPreviewIndex); | |
| } else { | |
| $("file-preview").textContent = "No files selected."; | |
| } | |
| } | |
| renderPreview(files, idx) { | |
| const f = files[idx]; | |
| if (!f) return; | |
| let content = f.content || ""; | |
| if (content.length > 8000) content = content.substring(0, 8000) + "\n\n... (truncated preview)"; | |
| $("file-preview").textContent = content; | |
| } | |
| renderExportSize(files) { | |
| const total = files.reduce((s, f) => s + (f.content ? f.content.length : 0), 0); | |
| $("export-size").textContent = files.length + " files " + humanSize(total); | |
| } | |
| async downloadBundle() { | |
| const files = this.applyExportOptions(); | |
| if (files.length === 0) { this.log("No files selected.", "err"); return; } | |
| if (typeof JSZip === "undefined") { | |
| this.log("JSZip library not available (CDN blocked?). Cannot build .zip.", "err"); | |
| return; | |
| } | |
| if (typeof saveAs === "undefined") { | |
| this.log("FileSaver library not available (CDN blocked?). Cannot download.", "err"); | |
| return; | |
| } | |
| this.log("Building .zip bundle..."); | |
| $("btn-download").disabled = true; | |
| try { | |
| const zip = new JSZip(); | |
| for (const f of files) zip.file(f.name, f.content); | |
| const blob = await zip.generateAsync({ type: "blob", compression: "DEFLATE", compressionOptions: { level: 6 } }); | |
| const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19); | |
| const name = "micro-distillery-" + ts + ".zip"; | |
| saveAs(blob, name); | |
| this.log("Bundle downloaded: " + name + " (" + humanSize(blob.size) + ")", "ok"); | |
| try { await this.storage.saveModel("latest", { timestamp: Date.now(), files: files.length }); } catch (e) { /* ignored */ } | |
| setTimeout(() => this.hideExportModal(), 800); | |
| } catch (e) { | |
| this.log("Export failed: " + e.message, "err"); | |
| } finally { | |
| $("btn-download").disabled = false; | |
| } | |
| } | |
| reset() { | |
| this.fetchDone = false; | |
| this.trainDone = false; | |
| this.fetching = false; | |
| this.training = false; | |
| this.transitions = new Map(); | |
| this.vocabCounts = new Map(); | |
| this.graph = new KnowledgeGraph(); | |
| this.kgSeed = 42; | |
| this.quantumMeasurements = []; | |
| this.corpus = FALLBACK_NAMES.slice(); | |
| this.cache = new Map(); | |
| this.metrics = { trainingSteps: 0, finalLoss: 0, averageReward: 0, cacheHits: 0, cacheMisses: 0, filteredCount: 0, totalSamples: 0, vaeLoss: 0 }; | |
| this.superposition = new Superposition4(1337); | |
| this.apiFanOut = new APIFanOut((m, c) => this.apiLog(m, c)); | |
| this.lastCollapse = null; | |
| this.selectedPreviewIndex = 0; | |
| this.modelFiles = []; | |
| this.markStep("fetch", "active", ""); | |
| this.markStep("train", "", ""); | |
| this.markStep("export", "", ""); | |
| this.updateMetrics(); | |
| this.renderGraph(); | |
| this.setProgress(0, "idle"); | |
| this.setProgressLabel("idle"); | |
| this.updateOscDisplay(); | |
| $("osc-result").textContent = ""; | |
| const os = $("osc-status"); | |
| if (os) os.textContent = "superposition"; | |
| this.log("System reset."); | |
| } | |
| } | |
| class Wizard { | |
| constructor(engine) { | |
| this.engine = engine; | |
| this.step = 0; | |
| this.steps = ["welcome", "fetch", "train", "export", "done"]; | |
| } | |
| open() { | |
| this.step = 0; | |
| this.render(); | |
| $("wizard").classList.remove("hidden"); | |
| requestAnimationFrame(() => $("wizard").classList.add("show")); | |
| } | |
| close() { | |
| $("wizard").classList.remove("show"); | |
| setTimeout(() => $("wizard").classList.add("hidden"), 200); | |
| } | |
| progressDots() { | |
| return this.steps.map((_, i) => { | |
| let cls = "wiz-dot"; | |
| if (i < this.step) cls += " done"; | |
| else if (i === this.step) cls += " active"; | |
| return '<div class="' + cls + '"></div>'; | |
| }).join(""); | |
| } | |
| render() { | |
| const id = this.steps[this.step]; | |
| $("wiz-progress").innerHTML = this.progressDots(); | |
| const content = { | |
| welcome: { | |
| title: "Micro Distillery", | |
| sub: "Build a small language model in 3 automated steps.", | |
| body: [ | |
| "<p>This tool builds a runnable character-level language model from free open data (HuggingFace + GitHub, 2026 sources). No installation needed.</p>", | |
| "<p>You will:</p>", | |
| "<ul>", | |
| "<li><strong>Fetch</strong> a training corpus from open endpoints (SmolLM2, Qwen2.5, OLMo-2 cards + Karpathy's makemore corpus)</li>", | |
| "<li><strong>Train</strong> the model with GRPO + VAE filtering</li>", | |
| "<li><strong>Export</strong> a HuggingFace-ready .zip bundle</li>", | |
| "</ul>", | |
| '<div class="wiz-info">', | |
| "<strong>Time:</strong> ~30 seconds total. <strong>Cost:</strong> free. <strong>Data:</strong> only from public sources.", | |
| "</div>" | |
| ], | |
| next: "Begin", | |
| back: null | |
| }, | |
| fetch: { | |
| title: "Step 1 - Fetch Data", | |
| sub: "Pull a training corpus from free, open endpoints.", | |
| body: [ | |
| "<p>We will fetch:</p>", | |
| "<ul>", | |
| "<li>Character corpus from <code>raw.githubusercontent.com/karpathy/makemore</code></li>", | |
| "<li>Model metadata from <code>huggingface.co/api/models?search=smollm2</code> (SmolLM2)</li>", | |
| "<li>Model metadata from <code>huggingface.co/api/models?search=qwen2.5-0.5b</code> (Qwen2.5)</li>", | |
| "<li>Reference cards from SmolLM2-135M and OLMo-2-1B</li>", | |
| "</ul>", | |
| "<p>If the network is unavailable, a bundled fallback corpus (200+ names) is used instead.</p>", | |
| '<div class="wiz-info">', | |
| '<strong>Status:</strong> <span id="wiz-fetch-status">not started</span>', | |
| "</div>", | |
| '<button class="btn btn-primary btn-lg btn-block" id="wiz-run-fetch">Fetch Data</button>' | |
| ], | |
| next: "Continue", | |
| back: null | |
| }, | |
| train: { | |
| title: "Step 2 - Train Model", | |
| sub: "Run 60 GRPO steps with VAE filtering.", | |
| body: [ | |
| "<p>Training optimizes a character bigram model using:</p>", | |
| "<ul>", | |
| "<li><strong>GRPO</strong> - group-relative policy optimization on sampled trajectories</li>", | |
| "<li><strong>VAE filter</strong> - keeps only high-probability trajectories</li>", | |
| "</ul>", | |
| '<div class="wiz-info">', | |
| '<strong>Status:</strong> <span id="wiz-train-status">waiting for data</span>', | |
| "</div>", | |
| '<button class="btn btn-primary btn-lg btn-block" id="wiz-run-train">Train Model</button>' | |
| ], | |
| next: "Continue", | |
| back: null | |
| }, | |
| export: { | |
| title: "Step 3 - Export Bundle", | |
| sub: "Download a HuggingFace-ready .zip.", | |
| body: [ | |
| "<p>The bundle includes:</p>", | |
| "<ul>", | |
| "<li><code>model.json</code>, <code>config.json</code>, <code>vocab.json</code></li>", | |
| "<li><code>README.md</code> model card (HF-formatted with YAML frontmatter)</li>", | |
| "<li><code>test.py</code>, <code>test.sh</code>, <code>test_readme.md</code></li>", | |
| "<li><code>diagrams/pipeline.svg</code>, <code>diagrams/architecture.svg</code></li>", | |
| "<li><code>to_parquet.py</code> for parquet conversion</li>", | |
| "<li><code>LICENSE</code>, <code>.gitattributes</code>, <code>requirements.txt</code></li>", | |
| "</ul>", | |
| '<div class="wiz-info">', | |
| '<strong>Status:</strong> <span id="wiz-export-status">ready</span>', | |
| "</div>", | |
| '<button class="btn btn-primary btn-lg btn-block" id="wiz-run-export">Open Export Panel</button>' | |
| ], | |
| next: "Done", | |
| back: null | |
| }, | |
| done: { | |
| title: "Complete", | |
| sub: "Your bundle has been downloaded.", | |
| body: [ | |
| "<p>Next steps:</p>", | |
| "<ul>", | |
| "<li>Unzip the bundle</li>", | |
| "<li>Run <code>./test.sh</code> to validate and chat</li>", | |
| "<li>Upload the folder to HuggingFace via <code>huggingface-cli upload</code></li>", | |
| "<li>Or run <code>python3 to_parquet.py</code> to convert to parquet</li>", | |
| "</ul>", | |
| '<div class="wiz-info">', | |
| "<strong>Tip:</strong> Click <em>Open Workspace</em> for advanced controls (graph, oscillator, custom settings).", | |
| "</div>" | |
| ], | |
| next: "Open Workspace", | |
| back: null | |
| } | |
| }[id]; | |
| $("wiz-title").textContent = content.title; | |
| $("wiz-sub").textContent = content.sub; | |
| $("wiz-content").innerHTML = content.body.join(""); | |
| $("wiz-back").style.visibility = this.step === 0 ? "hidden" : "visible"; | |
| $("wiz-next").textContent = content.next; | |
| if (id === "welcome" || id === "done") { | |
| $("wiz-skip").textContent = id === "done" ? "Close" : "Skip wizard"; | |
| } else { | |
| $("wiz-skip").textContent = "Skip wizard"; | |
| } | |
| if (id === "fetch") { | |
| const btn = $("wiz-run-fetch"); | |
| if (this.engine.fetching && btn) btn.disabled = true; | |
| if (btn) btn.addEventListener("click", async () => { | |
| if (this.engine.fetching) { | |
| $("wiz-fetch-status").textContent = "fetch already in progress"; | |
| return; | |
| } | |
| btn.disabled = true; | |
| $("wiz-fetch-status").textContent = "fetching..."; | |
| const ok = await this.engine.fetch(); | |
| $("wiz-fetch-status").textContent = ok ? "complete - " + this.engine.corpus.length + " lines" : "failed"; | |
| btn.disabled = false; | |
| if (ok) setTimeout(() => this.next(), 500); | |
| }); | |
| } | |
| if (id === "train") { | |
| const btn = $("wiz-run-train"); | |
| if (!this.engine.fetchDone) { | |
| $("wiz-train-status").textContent = "data required - go back"; | |
| if (btn) btn.disabled = true; | |
| } | |
| if (btn) btn.addEventListener("click", async () => { | |
| btn.disabled = true; | |
| $("wiz-train-status").textContent = "training..."; | |
| const ok = await this.engine.train(); | |
| $("wiz-train-status").textContent = ok ? "complete - loss " + this.engine.metrics.finalLoss.toFixed(4) : "failed"; | |
| btn.disabled = false; | |
| if (ok) setTimeout(() => this.next(), 500); | |
| }); | |
| } | |
| if (id === "export") { | |
| const btn = $("wiz-run-export"); | |
| if (!this.engine.trainDone) { | |
| $("wiz-export-status").textContent = "training required - go back"; | |
| if (btn) btn.disabled = true; | |
| } | |
| if (btn) btn.addEventListener("click", async () => { | |
| btn.disabled = true; | |
| $("wiz-export-status").textContent = "opening export panel..."; | |
| await this.engine.showExportModal(); | |
| $("wiz-export-status").textContent = "ready - configure options in the export panel"; | |
| btn.disabled = false; | |
| }); | |
| } | |
| } | |
| next() { | |
| if (this.step === this.steps.length - 1) { this.close(); return; } | |
| if (this.steps[this.step] === "fetch" && !this.engine.fetchDone) { | |
| this.engine.log("Fetch data before continuing.", "warn"); | |
| return; | |
| } | |
| if (this.steps[this.step] === "train" && !this.engine.trainDone) { | |
| this.engine.log("Train the model before continuing.", "warn"); | |
| return; | |
| } | |
| this.step++; | |
| this.render(); | |
| } | |
| prev() { if (this.step > 0) { this.step--; this.render(); } } | |
| } | |
| let engine; | |
| let wizard; | |
| function setupTabs() { | |
| document.querySelectorAll(".tab").forEach((tab) => { | |
| tab.addEventListener("click", () => { | |
| const id = tab.dataset.tab; | |
| document.querySelectorAll(".tab").forEach((t) => t.classList.remove("active")); | |
| document.querySelectorAll(".tabpanel").forEach((p) => p.classList.remove("active")); | |
| tab.classList.add("active"); | |
| const panel = document.querySelector('[data-tabpanel="' + id + '"]'); | |
| if (panel) panel.classList.add("active"); | |
| if (id === "graph") engine.renderGraph(); | |
| }); | |
| }); | |
| } | |
| function setupSliders() { | |
| const bindings = [ | |
| ["cfg-group", "val-group", 0], | |
| ["cfg-kl", "val-kl", 3], | |
| ["cfg-latent", "val-latent", 0], | |
| ["cfg-filter", "val-filter", 2] | |
| ]; | |
| for (const [id, display, decimals] of bindings) { | |
| const sl = $(id); | |
| const vd = $(display); | |
| if (!sl || !vd) continue; | |
| sl.addEventListener("input", () => { | |
| vd.textContent = parseFloat(sl.value).toFixed(decimals); | |
| }); | |
| } | |
| } | |
| function setupCloseButtons() { | |
| document.querySelectorAll("[data-close]").forEach((btn) => { | |
| btn.addEventListener("click", () => { | |
| const id = btn.dataset.close; | |
| if (id === "export-modal") engine.hideExportModal(); | |
| }); | |
| }); | |
| document.querySelectorAll(".modal").forEach((modal) => { | |
| modal.addEventListener("click", (e) => { | |
| if (e.target === modal) { | |
| if (modal.id === "export-modal") engine.hideExportModal(); | |
| if (modal.id === "wizard") wizard.close(); | |
| } | |
| }); | |
| }); | |
| } | |
| function setupExportOptions() { | |
| // Keep the file list, preview, and total size in sync with checkbox state. | |
| // No-op when the bundle hasn't been built yet (modelFiles empty). | |
| const boxes = document.querySelectorAll(".opt input[type='checkbox']"); | |
| boxes.forEach((cb) => { | |
| cb.addEventListener("change", () => { | |
| if (!engine || !engine.modelFiles || engine.modelFiles.length === 0) return; | |
| const filtered = engine.applyExportOptions(); | |
| engine.renderFileList(filtered); | |
| engine.renderExportSize(filtered); | |
| }); | |
| }); | |
| } | |
| function setupButtons() { | |
| $("btn-fetch").addEventListener("click", async () => { | |
| $("btn-fetch").disabled = true; | |
| await engine.fetch(); | |
| $("btn-fetch").disabled = false; | |
| }); | |
| $("btn-train").addEventListener("click", async () => { | |
| $("btn-train").disabled = true; | |
| await engine.train(); | |
| $("btn-train").disabled = false; | |
| }); | |
| $("btn-stop").addEventListener("click", () => engine.stop()); | |
| $("btn-export").addEventListener("click", () => engine.showExportModal()); | |
| $("btn-export-2").addEventListener("click", () => engine.showExportModal()); | |
| $("btn-wizard").addEventListener("click", () => wizard.open()); | |
| $("btn-reset").addEventListener("click", () => { | |
| if (confirm("Reset all state? This clears the model and metrics.")) engine.reset(); | |
| }); | |
| $("btn-download").addEventListener("click", () => engine.downloadBundle()); | |
| $("btn-graph-render").addEventListener("click", () => { | |
| engine.graph.layout2D(engine.kgSeed); | |
| engine.renderGraph(); | |
| }); | |
| $("btn-graph-save").addEventListener("click", async () => { | |
| try { await engine.storage.saveGraph(engine.graph); engine.log("Graph saved to IndexedDB.", "ok"); } | |
| catch (e) { engine.log("Save failed: " + e.message, "err"); } | |
| }); | |
| $("btn-graph-load").addEventListener("click", async () => { | |
| try { engine.graph = await engine.storage.loadGraph(); engine.renderGraph(); engine.log("Graph loaded.", "ok"); } | |
| catch (e) { engine.log("Load failed: " + e.message, "err"); } | |
| }); | |
| $("btn-graph-clear").addEventListener("click", async () => { | |
| try { await engine.storage.clearGraph(); engine.graph = new KnowledgeGraph(); engine.renderGraph(); engine.log("Graph cleared.", "ok"); } | |
| catch (e) { engine.log("Clear failed: " + e.message, "err"); } | |
| }); | |
| $("btn-osc-measure").addEventListener("click", () => engine.measureOsc()); | |
| $("wiz-next").addEventListener("click", () => wizard.next()); | |
| $("wiz-back").addEventListener("click", () => wizard.prev()); | |
| $("wiz-skip").addEventListener("click", () => wizard.close()); | |
| } | |
| function boot() { | |
| engine = new Engine(); | |
| wizard = new Wizard(engine); | |
| engine.log("Micro Distillery v3.1.2 initialized."); | |
| engine.log("Data sources (2026): HuggingFace + GitHub"); | |
| engine.log("Click Setup Wizard in the header, or use the pipeline."); | |
| engine.log("Ready."); | |
| window.addEventListener("error", (e) => { | |
| try { engine.log("Unhandled: " + (e.message || e.error || e), "err"); } catch (_) {} | |
| }); | |
| window.addEventListener("unhandledrejection", (e) => { | |
| const r = e && e.reason; | |
| const m = r && r.message ? r.message : String(r); | |
| try { engine.log("Unhandled rejection: " + m, "err"); } catch (_) {} | |
| }); | |
| setupTabs(); | |
| setupSliders(); | |
| setupCloseButtons(); | |
| setupExportOptions(); | |
| setupButtons(); | |
| engine.updateMetrics(); | |
| engine.updateOscDisplay(); | |
| engine.renderGraph(); | |
| engine.setProgress(0, "idle"); | |
| let seen = null; | |
| try { seen = localStorage.getItem("md_wizard_seen"); } catch (_) {} | |
| if (!seen) { | |
| setTimeout(() => { | |
| try { wizard.open(); } catch (_) {} | |
| try { localStorage.setItem("md_wizard_seen", "1"); } catch (_) {} | |
| }, 400); | |
| } | |
| const offlineBar = $("offline-bar"); | |
| const updateOnline = () => offlineBar.classList.toggle("hidden", navigator.onLine); | |
| window.addEventListener("online", updateOnline); | |
| window.addEventListener("offline", updateOnline); | |
| updateOnline(); | |
| window.addEventListener("resize", () => engine.renderGraph()); | |
| if ("serviceWorker" in navigator) { | |
| navigator.serviceWorker.register("sw.js").catch(() => { /* expected when sw.js absent */ }); | |
| } | |
| } | |
| window.addEventListener("load", boot); | |
| </script> | |
| </body> | |
| </html> |