File size: 40,676 Bytes
bb98842
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="color-scheme" content="dark"><title>AGILLM · Distributed training progress</title>
<style>
:root{color-scheme:dark;--bg:#080e14;--panel:#111c27;--line:#243342;--muted:#a6b5c4;--text:#edf4fa;--accent:#86e9bc;--blue:#8ebfff}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font:16px/1.6 system-ui,sans-serif}main{max-width:1160px;margin:auto;padding:36px 24px 56px}header{display:flex;justify-content:space-between;gap:20px;align-items:center;border-bottom:1px solid var(--line);padding-bottom:22px}.brand{font-weight:800;letter-spacing:.09em}.brand span{color:var(--accent)}.pill{font-size:13px;border:1px solid var(--line);border-radius:30px;padding:6px 12px;color:var(--muted)}.hero{padding:48px 0 28px;max-width:850px}.eyebrow{color:var(--accent);font-size:12px;letter-spacing:.15em;font-weight:700;text-transform:uppercase}h1{font-size:clamp(32px,5vw,54px);line-height:1.1;letter-spacing:-.04em;margin:12px 0 20px}h2{font-size:19px;margin:0 0 8px}p{margin:8px 0;color:var(--muted)}a{color:var(--accent);text-underline-offset:4px}button{background:var(--accent);border:0;color:#092015;border-radius:8px;padding:10px 16px;cursor:pointer;font:inherit;font-weight:700}button:disabled{opacity:.55;cursor:wait}button:focus-visible,a:focus-visible{outline:3px solid var(--blue);outline-offset:5px}.livebar{display:flex;gap:16px;align-items:center;justify-content:space-between;margin:4px 0 22px;font-size:13px}.livebar p{margin:0}#connection{color:var(--accent)}.metrics{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}.metric,.card{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:22px}.metric strong{display:block;font-size:clamp(25px,3vw,36px);line-height:1.3;font-variant-numeric:tabular-nums;margin:8px 0}.label{font-size:13px;color:var(--muted)}.small{font-size:12px;color:var(--muted)}.columns{display:grid;grid-template-columns:1.1fr 1fr;gap:18px;margin-top:18px}.barrow{margin:17px 0}.barlabel{display:flex;justify-content:space-between;gap:10px;font-size:13px;margin-bottom:7px}.track{height:9px;background:#243342;border-radius:8px;overflow:hidden}.fill{height:100%;background:var(--accent);border-radius:8px}.chart{height:170px;display:flex;align-items:center;justify-content:center;margin-top:16px}.chart svg{width:100%;height:100%;overflow:visible}.evals{display:flex;justify-content:space-between;gap:10px;font-size:12px;color:var(--muted)}.note{border-left:3px solid var(--blue);padding:0 0 0 14px;font-size:14px;margin-top:22px}.join{margin-top:18px;display:grid;grid-template-columns:.9fr 1.3fr;gap:28px}.join ol{padding-left:20px;color:var(--muted)}pre{background:#080e14;border:1px solid var(--line);padding:16px;border-radius:9px;overflow:auto;font-size:12px;line-height:1.7;white-space:pre-wrap;overflow-wrap:anywhere}code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace}.metadata{display:flex;flex-wrap:wrap;gap:8px;margin:15px 0}.metadata span{border:1px solid var(--line);padding:4px 10px;border-radius:5px;font-size:12px;color:var(--muted)}footer{margin-top:28px;border-top:1px solid var(--line);padding-top:20px;color:var(--muted);font-size:12px}.warn{color:#ffca84!important}#error{color:#ffca84;min-height:1px}.sample{white-space:pre-wrap;overflow-wrap:anywhere;font:14px/1.7 ui-monospace,monospace;color:#d1e0ec;margin:15px 0 0}details{margin-top:18px}summary{cursor:pointer;color:var(--blue)}@media(max-width:760px){main{padding:22px 16px}.hero{padding:32px 0 22px}.metrics{grid-template-columns:repeat(2,1fr)}.columns,.join{grid-template-columns:1fr}.metric,.card{padding:18px}.livebar{align-items:flex-start}.pill{font-size:11px}header{gap:8px}.brand{font-size:14px}}@media(prefers-reduced-motion:no-preference){.fill{transition:width .4s ease}}
</style></head><body><main>
<header><div class="brand">AGILLM <span>/ NETWORK</span></div><span class="pill">Experimental training pilot</span></header>
<nav class="small" aria-label="DBMoE pages"><a href="/dbmoe/browser-selftest/">Test this browser</a> · <a href="/dbmoe/">Native worker setup</a> · <a href="https://github.com/Marxist-Leninist/dbmoe" rel="noopener noreferrer">Source code</a></nav>
<section class="hero"><div class="eyebrow">DiffusionBlocks · Mixture of Experts</div><h1>Many computers.<br>One shared experiment.</h1><p>Computers anywhere train separate blocks of one model and run it together. Join with one command, or try it below.</p><p class="note">This is the small byte-level pilot, not the 512B model.</p></section>
<div class="livebar"><div><p id="connection" role="status" aria-live="polite">Connecting to live metrics…</p><p id="updated">Refreshes every 15 seconds while this page is visible.</p></div><button id="refresh" type="button">Refresh</button></div><p id="error" role="alert"></p>
<section class="metrics" aria-label="Training totals"><div class="metric"><span class="label">Accepted updates</span><strong id="updates">—</strong><span class="small">Accepted and merged by the coordinator</span></div><div class="metric"><span class="label">Training bytes processed</span><strong id="bytes">—</strong><span class="small">Bytes of text trained on</span></div><div class="metric"><span class="label">Workers training now</span><strong id="workers">—</strong><span class="small">Have a job right now</span></div><div class="metric"><span class="label">Latest held-out bits / byte</span><strong id="bpb">—</strong><span class="small">Top block on text no worker sees · lower is better</span></div></section>
<section class='card' style='margin-top:18px' aria-label='Accepted training and reference targets' id='tgSection'><div class='eyebrow'>Training · whole network</div><h2 style='margin-top:8px'>Bytes trained on</h2><strong id='tgDone' style='display:block;font-size:clamp(28px,4vw,40px);line-height:1.25;font-variant-numeric:tabular-nums;margin:6px 0'>—</strong><p id='tgRate' class='small'>Measuring the network rate…</p><div class='barrow'><div class='barlabel'><span id='tgCLabel'>Chinchilla: 20 × parameters</span><span id='tgCPct'>—</span></div><div class='track'><div class='fill' id='tgCBar' style='width:0%'></div></div><p class='small' id='tgCLeft'>—</p></div><div class='barrow'><div class='barlabel'><span>500 billion bytes</span><span id='tg5Pct'>—</span></div><div class='track'><div class='fill' id='tg5Bar' style='width:0%;background:var(--blue)'></div></div><p class='small' id='tg5Left'>—</p></div><div class='barrow'><div class='barlabel'><span>1 trillion bytes</span><span id='tg1Pct'>—</span></div><div class='track'><div class='fill' id='tg1Bar' style='width:0%;background:#c9a7ff'></div></div><p class='small' id='tg1Left'>—</p></div><p class='small' id='tgNote'>Only bytes from accepted updates count, and text can repeat.</p><script>(()=>{'use strict';const el=id=>document.getElementById(id),nf=new Intl.NumberFormat('en-GB');const short=n=>{const a=Math.abs(n);for(const [v,u] of [[1e12,' T'],[1e9,' B'],[1e6,' M'],[1e3,' K']]){if(a>=v){const x=n/v;return String(+x.toFixed(x>=100?0:x>=10?1:2))+u}}return nf.format(Math.round(n))};const pct=x=>x>=100?x.toFixed(0)+'%':x>=10?x.toFixed(1)+'%':x>=1?x.toFixed(2)+'%':x>=0.01?x.toFixed(3)+'%':x>0?x.toPrecision(2)+'%':'0%';const dur=s=>{if(!Number.isFinite(s)||s<=0)return null;const m=s/60,h=m/60,d=h/24,y=d/365.25;if(y>=2)return y.toFixed(1)+' years';if(d>=60)return Math.round(d/30.44)+' months';if(d>=2)return d.toFixed(d>=10?0:1)+' days';if(h>=2)return h.toFixed(1)+' hours';if(m>=2)return Math.round(m)+' minutes';return Math.round(s)+' seconds'};const when=s=>new Date(Date.now()+s*1000).toLocaleDateString('en-GB',{day:'numeric',month:'short',year:'numeric'});let rate=null,last=null;function row(k,target,done,unit){const p=100*done/target;el('tg'+k+'Pct').textContent=done>=target?'Reached · '+pct(p):pct(p);el('tg'+k+'Bar').style.width=Math.min(100,Math.max(p,done>0?0.6:0))+'%';const left=target-done;if(left<=0){el('tg'+k+'Left').textContent='Reached: '+short(done)+' of '+short(target)+' '+unit+' ('+(done/target).toFixed(2)+'×).';return}const t=rate>0?dur(left/rate):null;el('tg'+k+'Left').textContent=short(left)+' '+unit+' to go'+(t?' · about '+t+' at this rate (around '+when(left/rate)+')':'')+'.'}async function tick(){if(document.hidden&&last)return;try{const r=await fetch('./status',{cache:'no-store',signal:AbortSignal.timeout(10000)});if(!r.ok)throw new Error(String(r.status));const s=await r.json();if(!Number.isFinite(s.tokens)||!Number.isFinite(s.params)||s.params<=0)throw new Error('format');const now=Date.now(),t=s.training_throughput;let inst=null;if(t&&t.state==='measured'&&Number.isFinite(t.bytes_per_s)&&t.bytes_per_s>=0)inst=t.bytes_per_s;else if(last&&s.tokens>=last.tokens&&now-last.time>=5000)inst=(s.tokens-last.tokens)/((now-last.time)/1000);if(last&&s.tokens<last.tokens)rate=null;last={time:now,tokens:s.tokens};if(inst!==null)rate=rate===null?inst:0.8*rate+0.2*inst;const unit='bytes';el('tgDone').textContent=nf.format(s.tokens);el('tgRate').textContent=rate>0?'About '+short(rate)+' bytes a second, '+short(rate*86400)+' a day, at the current rate.':'Measuring the network rate…';const chin=20*s.params;el('tgCLabel').textContent='Chinchilla: 20 × '+short(s.params)+' parameters = '+short(chin)+' bytes';row('C',chin,s.tokens,unit);row('5',5e11,s.tokens,unit);row('1',1e12,s.tokens,unit);el('tgNote').textContent='Only bytes from accepted updates count, and text can repeat. The Chinchilla rule was measured on subword tokens, so for bytes it is a rough mark.'}catch(e){el('tgRate').textContent='Waiting for live metrics…'}}tick();setInterval(tick,15000);document.addEventListener('visibilitychange',()=>{if(!document.hidden)tick()})})();</script></section>
<section class="columns" aria-label="Network throughput"><div class="card"><h2>Network training throughput</h2><strong id="trainRate" style="font-size:32px;font-variant-numeric:tabular-nums">Measuring…</strong><p class="small">Bytes of text trained on per second, from accepted updates</p><p id="rateWindow" class="small">Collecting at least 30 seconds of coordinator counter samples.</p><p class="small">Anonymous contributions are tested before merging, including learning gain and held-out text checks. Trusted workers can bypass these checks. Rejected updates are not counted.</p><p id="usefulNow" class="small"></p><p class="small">These are bytes, not the subword tokens larger models count.</p></div><div class="card"><h2>Network inference throughput</h2><strong id="inferRate" style="font-size:32px;font-variant-numeric:tabular-nums">Connecting…</strong><p class="small">Bytes generated per second on the network, over the last hour's generations. Each byte travels through the blocks as a chain of hops, each run by a worker holding that block.</p><p id="inferDetail" class="small"></p></div></section><div class="columns"><section class="card"><h2>Work across the blocks</h2><p class="small">Updates merged per block. Blocks with the most left to learn get the most work.</p><div id="blocks"><p>Waiting for block metrics…</p></div></section><section class="card"><h2>Evaluation history</h2><p class="small">Held-out denoising loss for the top block, in bits per byte. Lower is better.</p><div id="chart" class="chart"><p>No evaluation loaded yet.</p></div><div class="evals"><span id="firstEval">—</span><span id="lastEval">—</span></div><p id="evalTime" class="small"></p><p id="genQuality" class="small"></p><details><summary>Latest saved model sample</summary><p class="small">What the model wrote at the last evaluation, unedited.</p><div class="sample" id="sample">No sample available.</div></details></section></div>
<section class="card" style="margin-top:18px" aria-label="Network workers"><div class="eyebrow">Participation</div><h2 style="margin-top:8px">Workers on this run</h2><p id="workerSummary">Waiting for worker records…</p><p id="newWorkers" class="small" role="status" aria-live="polite"></p><p class="small">One card per worker. Green means it checked in within the last 45 seconds or merged an update in the last 2 minutes; amber means neither. Anyone leaving for good can take their own cards off the list.</p><div style="margin:12px 0"><p id="ownerStatus" class="small" role="status" aria-live="polite">Checking for an existing private owner session…</p><p class="small">Signed in as the owner, your workers show in blue.</p><a href="/agillm-linger/" id="ownerLogin">Sign in with the existing owner login</a></div><div id="workerCards" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,225px),1fr));gap:12px;margin-top:18px"></div><p class="small" style="margin-top:14px">No names, hostnames or addresses are shown. Hardware is what each worker reports about itself. Speed is measured by the coordinator.</p></section><section class="card" style="margin-top:18px" aria-label="Try the model" id="try-model"><div class="eyebrow">Public inference demo</div><h2 style="margin-top:8px">Try a short continuation</h2><p>Choose where inference runs: Automatic measures both real routes and selects the faster one; Single coordinator keeps computation on the coordinator; Distributed only requires participant workers and never substitutes the coordinator; Network keeps the existing coordinator-fallback behaviour. Every response stays pinned to one checkpoint snapshot. The sampler separately chooses the full chain or only the top block. It's a small model early in training, so expect broken text.</p><p class="small">Do not enter private information or secrets. Your text goes to the coordinator and, whenever a distributed route is used, to participants' computers. Automatic calibration itself uses only fixed public text.</p><label for="prompt" style="display:block;margin-top:16px">Starting text</label><textarea id="prompt" maxlength="200" rows="3" style="width:100%;resize:vertical;background:#080e14;color:var(--text);border:1px solid var(--line);border-radius:8px;padding:12px;font:inherit">The </textarea><div style="display:flex;gap:16px;align-items:center;flex-wrap:wrap;margin:14px 0"><label for="length">Bytes</label><select id="length" style="background:#080e14;color:var(--text);padding:10px;border:1px solid var(--line);border-radius:8px"><option value="32">32</option><option value="64">64</option></select><label for="inferenceRoute">Route</label><select id="inferenceRoute" style="background:#080e14;color:var(--text);padding:10px;border:1px solid var(--line);border-radius:8px"><option value="auto" selected>Automatic · fastest measured</option><option value="single">Single coordinator</option><option value="distributed">Distributed only · no fallback</option><option value="network">Network with coordinator fallback</option></select><label for="sampler">Sampler</label><select id="sampler" style="background:#080e14;color:var(--text);padding:10px;border:1px solid var(--line);border-radius:8px"><option value="chain">Every block (full chain)</option><option value="top">Top block only, clearest text for now</option></select><button id="generate" type="button">Generate</button><span id="generationStatus" class="small" role="status" aria-live="polite">Ready when you are.</span></div><pre id="generated" aria-label="Generated text" style="min-height:80px;white-space:pre-wrap">Your continuation will appear here.</pre><p id="route" class="small" style="overflow-wrap:anywhere"></p><p class="small">Automatic measures one byte on single and distributed for each new snapshot/sampler and uses the faster route; the stream reports the selected route and probe timings.</p><p class="small">One generation at a time across the whole network, so you may need to wait a moment. No account needed.</p></section><section class="card join"><div><div class="eyebrow">Contribute compute</div><h2 style="margin-top:8px">Join with one command</h2><p>No account and nobody to ask. Your worker gets its own credential, trains one block at a time and serves that block for network inference. A GPU is optional. Contributions use compute, power and bandwidth.</p><ol><li>On Linux or macOS, run the command. It installs what it needs in <code>~/dbmoe-worker</code> and starts.</li><li>On Windows, or to set it up by hand, use the second set of commands.</li><li>Leave it running as long as you like. Stop it with Ctrl+C and start it again whenever you want; it keeps its credential.</li><li>Leaving for good? Run the leave command: your workers come off the list and your credential ends. What you already contributed stays in the model. You can rejoin at any time.</li></ol><p class="small">Anonymous contributions are tested before they merge; trusted workers can bypass these checks. Your worker's card shows its CPU, RAM and GPU, and <code>--no-share-hardware</code> keeps them off the list. Keep credentials out of GitHub, screenshots and public messages.</p></div><div><p><a href="https://github.com/Marxist-Leninist/dbmoe" rel="noopener noreferrer">Worker source and setup guide on GitHub</a></p><pre><code># Linux or macOS
curl -fsSL https://codex.opentransformers.online/dbmoe/join.sh | sh

# Windows, or by hand (Windows: py instead of python3, curl.exe in PowerShell)
python3 -m pip install torch --index-url https://download.pytorch.org/whl/cpu
curl -fsSL https://codex.opentransformers.online/dbmoe/code -o dbmoe.py
python3 dbmoe.py worker

# Options: --device cuda|rocm|xpu|mps  --threads 4  --hours 1  --no-serve

# Leaving for good: takes you off the worker list
curl -fsSL https://codex.opentransformers.online/dbmoe/leave.sh | sh</code></pre><p class="small">Use only computers you own or are authorized to use. Source and setup guide: <a href="https://github.com/Marxist-Leninist/dbmoe" rel="noopener noreferrer">GitHub repository</a>.</p><details style="margin-top:18px"><summary>Use one identity across several machines</summary><p class="small">After installing the worker, create one contributor token. Copy its file securely to the other computers you own or are authorised to use. Start each machine with a different worker name; their contributions share the same identity.</p><pre><code># Create the shared identity once
python3 dbmoe.py enrol --token-file contributor.token

# On each machine, use its own name
python3 dbmoe.py worker --token @contributor.token --name machine-01</code></pre><p class="small">Keep contributor.token private. Change machine-01 for each computer. To join anonymously with a separate identity, use the normal worker command above; no account is needed.</p></details></div></section>
<section aria-label="Model configuration"><div class="metadata" id="metadata"></div><p class="small" id="source"></p></section>
<footer>Public progress and sample demo · No account required · Metrics come from the coordinator and refresh every 15 seconds.</footer>
</main><script>
'use strict';
const $=id=>document.getElementById(id), fmt=new Intl.NumberFormat('en-GB');
let busy=false,lastSuccess=0;
const samples=[];
const workerAliases=new Map();let initialWorkers=null;
const workerHistoryKey='dbmoe-public-worker-history-v1';let workerHistory={};
try{const old=JSON.parse(localStorage.getItem(workerHistoryKey)||'{}');for(const [id,r] of Object.entries(old))if(/^worker-[0-9a-f]{16}$/.test(id)&&r&&typeof r==='object')workerHistory[id]={updates:Number(r.updates)||0,last:typeof r.last==='string'?r.last:null,seen:Number(r.seen)||0};}catch(e){}
let latestWorkerReceived=0;
function hardwareLines(value){
if(!value||value.schema!==1||value.self_reported!==true||!['cpu','cuda','rocm','mps'].includes(value.backend))return null;
const safe=v=>typeof v==='string'?v.replace(/[\u0000-\u001f\u007f]/g,'').trim().slice(0,80):'';
const count=v=>Number.isInteger(v)&&v>=1&&v<=4096?v:null;
const memory=(v,max)=>Number.isFinite(v)&&v>0&&v<=max?v.toFixed(1)+' GiB':null;
const lines=['Training on: '+({cpu:'CPU',cuda:'NVIDIA GPU',rocm:'AMD GPU',mps:'Apple GPU'}[value.backend])];
lines.push('CPU: '+(safe(value.cpu_model)||'Not reported'));
if(safe(value.cpu_arch))lines.push('CPU architecture: '+safe(value.cpu_arch));
lines.push('Logical CPUs: '+(count(value.cpu_cores)||'Not reported'));
lines.push('Worker CPU threads: '+(count(value.cpu_threads)||'Not reported'));
lines.push('System RAM: '+(memory(value.ram_gib,1048576)||'Not reported'));
lines.push('Operating system: '+(safe(value.os)||'Not reported'));
if(value.backend!=='cpu'){
lines.push('GPU: '+(safe(value.gpu_name)||'Not reported'));
lines.push('GPU memory: '+(value.backend==='mps'?'Not reported (Metal)':memory(value.gpu_memory_gib,65536)||'Not reported'));
}
if(safe(value.torch_version))lines.push('PyTorch: '+safe(value.torch_version));
if(safe(value.runtime_version))lines.push('Runtime: '+safe(value.runtime_version));
return lines;
}
function workerState(record,lease,now,present,sourceStale,peer){
const stamp=Date.parse(record.last),age=Number.isFinite(stamp)?(now-stamp)/1000:Infinity;
if(sourceStale)return {label:'Status unknown · page data is old',colour:'#ffca84',rank:2};
if(peer)return {label:lease?'Online · training':'Online',colour:'var(--accent)',rank:0};
if(!present)return {label:'Not seen lately',colour:'#ffca84',rank:3};
if(age>=-30&&age<=120)return {label:lease?'Active · training':'Active',colour:'var(--accent)',rank:0};
if(lease&&Number(lease.age_s)<=120)return {label:'Active · just started a job',colour:'var(--accent)',rank:0};
return {label:lease?'Has a job, no recent update':'Not active',colour:'#ffca84',rank:1};
}
let ownerWorkers=new Set(),ownerTestCompletions={},latestWorkerSnapshot=null,latestWorkerClock=0,ownerView=false,ownerCheckBusy=false;
async function checkOwner(){
if(ownerCheckBusy)return;ownerCheckBusy=true;
try{const response=await fetch('./owner-workers',{cache:'no-store',credentials:'same-origin',signal:AbortSignal.timeout(5000)});
if(!response.ok)throw new Error('Owner session unavailable');
const data=await response.json();if(!Array.isArray(data.owned_worker_ids))throw new Error('Invalid owner map');
ownerWorkers=new Set(data.owned_worker_ids.filter(v=>/^worker-[0-9a-f]{16}$/.test(v)));ownerTestCompletions={};for(const [id,count] of Object.entries(data.completed_tests||{}))if(ownerWorkers.has(id)&&Number.isInteger(count)&&count>=0)ownerTestCompletions[id]=count;ownerView=true;
show('ownerStatus','Private owner view: '+ownerWorkers.size+' verified owner worker identities. Blue cards are yours. Verified '+(data.verified_at||'recently')+'.');
$('ownerLogin').hidden=true;
}catch(e){ownerWorkers.clear();ownerTestCompletions={};ownerView=false;show('ownerStatus','Public view: ownership is hidden. Sign in to identify your verified workers automatically.');$('ownerLogin').hidden=false;}
finally{ownerCheckBusy=false;if(latestWorkerSnapshot)workers(latestWorkerSnapshot,latestWorkerClock,false)}
}
function workers(s,serverNow,remember=true){
if(remember){latestWorkerSnapshot=s;latestWorkerClock=serverNow;latestWorkerReceived=Date.now();}
const records=s.workers&&typeof s.workers==='object'?s.workers:{};
const leases=Array.isArray(s.active_leases)?s.active_leases:[];
const leaseMap=new Map();for(const l of leases)if(typeof l.worker==='string')leaseMap.set(l.worker,l);
const peers=s.net_inference&&s.net_inference.peers&&typeof s.net_inference.peers==='object'?s.net_inference.peers:{};
const current=new Set([...Object.keys(records),...leaseMap.keys(),...Object.keys(peers)].filter(id=>/^worker-[0-9a-f]{16}$/.test(id)));
if(remember&&Array.isArray(s.left_workers))for(const id of s.left_workers)if(!current.has(id))delete workerHistory[id];
if(remember){for(const id of current){const r=records[id]||{},old=workerHistory[id]||{};workerHistory[id]={updates:Number.isFinite(Number(r.updates))?Number(r.updates):(old.updates||0),last:typeof r.last==='string'?r.last:(old.last||null),seen:serverNow};}
try{localStorage.setItem(workerHistoryKey,JSON.stringify(workerHistory))}catch(e){}}
const ids=Object.keys(workerHistory).sort();
for(const id of ids)if(!workerAliases.has(id))workerAliases.set(id,'Worker '+String(workerAliases.size+1).padStart(2,'0'));
if(initialWorkers===null&&remember)initialWorkers=new Set(ids);
const newcomers=initialWorkers?ids.filter(id=>!initialWorkers.has(id)):[];
const staleSource=lastSuccess===0||Date.now()-lastSuccess>45000;
const states=new Map(ids.map(id=>{const completed=ownerView&&ownerWorkers.has(id)&&!leaseMap.has(id)&&ownerTestCompletions[id]===workerHistory[id].updates;return [id,completed?{label:'Your test, completed',colour:'var(--blue)',rank:4}:workerState(workerHistory[id],leaseMap.get(id),serverNow,current.has(id),staleSource,peers[id])];}));
const recent=ids.filter(id=>{const age=serverNow-Date.parse(workerHistory[id].last);return current.has(id)&&age>=-30000&&age<=120000}).length;
const inactive=ids.filter(id=>states.get(id).rank>=1&&states.get(id).rank<=3).length;const completed=ids.filter(id=>states.get(id).rank===4).length;
const reported=Object.keys(peers).map(id=>(records[id]||{}).hardware).filter(Boolean),threads=reported.reduce((n,h)=>n+(Number.isInteger(h.cpu_threads)?h.cpu_threads:0),0),gpus=reported.filter(h=>h.backend&&h.backend!=='cpu').length;
show('workerSummary',ids.length+' workers · '+Object.keys(peers).length+' online · '+leaseMap.size+' training · '+inactive+' not active'+(threads?' · '+fmt.format(threads)+' CPU threads':'')+(gpus?' · '+gpus+(gpus===1?' GPU':' GPUs'):'')+(ownerView?' · '+completed+' completed owner tests':''));
show('newWorkers',newcomers.length?newcomers.length+' new since this page opened.':'');
$('workerCards').replaceChildren();
if(!ids.length){const empty=document.createElement('p');empty.textContent='No workers reported yet.';$('workerCards').append(empty);return}
ids.sort((a,b)=>states.get(a).rank-states.get(b).rank||workerAliases.get(a).localeCompare(workerAliases.get(b)));
ids.forEach(id=>{const r=workerHistory[id],lease=leaseMap.get(id),status=states.get(id),card=document.createElement('article');card.style.cssText='border:1px solid var(--line);border-radius:9px;padding:15px';if(ownerView&&ownerWorkers.has(id)){card.style.borderColor='var(--blue)';card.style.background='#142739'}
const title=document.createElement('strong');title.textContent=workerAliases.get(id)+(!initialWorkers||initialWorkers.has(id)?'':' · New')+(ownerView?(ownerWorkers.has(id)?' · Yours (verified)':' · Ownership unknown'):'');
const state=document.createElement('p');state.className='small';state.style.color=status.colour;state.textContent=status.label;
const count=document.createElement('p');count.className='small';count.textContent=fmt.format(Number(r.updates)||0)+' accepted '+(Number(r.updates)===1?'update':'updates')+(Number((records[id]||{}).refused)?' · '+fmt.format(Number(records[id].refused))+' refused':'');
const last=document.createElement('p');last.className='small';const stamp=Date.parse(r.last),age=(serverNow-stamp)/1000;last.textContent=!Number.isFinite(stamp)?'No accepted update yet':'Last update '+(age<60?'under a minute ago':age<7200?Math.floor(age/60)+' min ago':Math.floor(age/3600)+' hours ago');
const observed=document.createElement('p');observed.className='small';const speed=Number((records[id]||{}).measured_bytes_s);observed.textContent=[lease&&Number.isInteger(lease.block)?'Training block '+(lease.block+1)+' now':'',Number.isFinite(speed)&&speed>0?'Speed: '+fmt.format(Math.round(speed))+' bytes/s':''].filter(Boolean).join(' · ');
const peer=peers[id],serving=document.createElement('p');serving.className='small';serving.textContent=peer&&Number.isInteger(peer.block)?'Serving block '+(peer.block+1)+' · '+fmt.format(Number(peer.hops)||0)+(Number(peer.hops)===1?' hop':' hops')+' · '+fmt.format(Number(peer.ms)||0)+' ms per hop':'';
const alias=document.createElement('p');alias.className='small';alias.textContent='Anonymous ID: '+id;card.append(title,state,count,last,observed,serving,alias);const hardware=document.createElement('div');hardware.className='small';hardware.style.cssText='border-top:1px solid var(--line);margin-top:10px;padding-top:10px';const lines=hardwareLines((records[id]||{}).hardware);const heading=document.createElement('strong');heading.textContent=lines?'Hardware':'Hardware not shared';hardware.append(heading);if(lines){for(const line of lines){const p=document.createElement('p');p.className='small';p.textContent=line;hardware.append(p)}}card.append(hardware);$('workerCards').append(card)});
}
let generating=false,nextGeneration=0;
function generationButton(){const remaining=Math.max(0,Math.ceil((nextGeneration-Date.now())/1000));$('generate').disabled=generating||remaining>0;$('generate').textContent=generating?'Generating…':remaining?'Wait '+remaining+'s':'Generate'}
async function generateSample(){
if(generating||Date.now()<nextGeneration)return;
const prompt=$('prompt').value.slice(0,200);if(!prompt.trim()){show('generationStatus','Enter a little starting text first.');return}
const length=$('length').value==='64'?64:32;
const selectedRoute=$('inferenceRoute').value,routeLabel={auto:'Automatic',single:'Single coordinator',distributed:'Distributed only',network:'Network with fallback'}[selectedRoute];
generating=true;nextGeneration=Date.now()+60000;generationButton();show('generationStatus','Starting '+routeLabel.toLowerCase()+' inference…');show('generated',prompt);show('route','');
const start=performance.now(),lineDecoder=new TextDecoder(),byteDecoder=new TextDecoder();let text=prompt,pending='',count=0,finished=null;
const handle=ev=>{if(ev.t==='start'&&ev.requested_route==='auto'&&ev.auto_calibration){const m=ev.auto_calibration.measured||{},probe=name=>m[name]&&m[name].ok?Number(m[name].seconds).toFixed(2)+'s':'unavailable';show('route','Auto selected '+ev.route+' · one-byte probes: single '+probe('single')+', distributed '+probe('distributed')+(ev.auto_calibration.cached?' · cached for this snapshot/sampler':'')+'.');}else if(ev.t==='byte'){text+=byteDecoder.decode(new Uint8Array([ev.b]),{stream:true});count++;show('generated',text);show('generationStatus',routeLabel+' · '+count+' of '+length+' bytes…');show('route','Last byte: '+ev.hops.map(h=>'Block '+(h.block+1)+' on '+(h.via==='coordinator'?'the coordinator':(workerAliases.get(h.via)||h.via))+' ('+h.ms+' ms)').join(' → '))}else if(ev.t==='done')finished=ev;else if(ev.t==='error')throw new Error(ev.error)};
try{const sampler=$('sampler').value==='top'?'top':'chain';const q=new URLSearchParams({net:'1',route:selectedRoute,prompt,n:String(length),sampler,temp:sampler==='top'?'0.75':'0',seed:String(Math.floor(Math.random()*2147483647))});
const response=await fetch('./sample?'+q,{cache:'no-store',signal:AbortSignal.timeout(300000)});
if(response.status===409){show('generationStatus',selectedRoute==='distributed'?'Distributed route unavailable: not every required block has a worker on the same snapshot. No coordinator fallback was used.':selectedRoute==='auto'?'Automatic calibration could not complete on either route. No result received.':'The requested route or snapshot is unavailable. No result received.');show('generated','No result received.');return}
if(response.status===429||response.status===503){show('generationStatus','The shared demo is busy or rate-limited. Try again after the countdown.');show('generated','No result received.');return}
if(!response.ok||!response.body)throw new Error('Sample unavailable');
const reader=response.body.getReader();
for(;;){const {value,done}=await reader.read();if(done)break;pending+=lineDecoder.decode(value,{stream:true});const lines=pending.split('\n');pending=lines.pop();lines.filter(Boolean).forEach(l=>handle(JSON.parse(l)))}
if(!finished)throw new Error('Stream ended early');
const elapsed=Math.max((performance.now()-start)/1000,0.001);
show('generationStatus','Done · '+routeLabel+(selectedRoute==='auto'&&finished.route?' → '+finished.route:'')+' · '+(count/elapsed).toFixed(2)+' bytes/s · '+elapsed.toFixed(1)+'s from button to done · '+fmt.format(finished.hops_network)+' hops on '+finished.workers+(finished.workers===1?' worker':' workers')+(finished.hops_coordinator?' · '+fmt.format(finished.hops_coordinator)+' on the coordinator':'')+'.');refresh();
}catch(e){show('generationStatus',selectedRoute==='distributed'?'Distributed generation stopped early. No coordinator fallback was used. Wait before trying again.':'The generation stopped early. Wait before trying again.');if(text===prompt)show('generated','Generation did not complete in this browser.');}
finally{generating=false;generationButton()}
}
$('generate').addEventListener('click',generateSample);setInterval(generationButton,1000);
const number=v=>Number.isFinite(Number(v))?Number(v):null;
function show(id,value){$(id).textContent=value}
function rate(s,serverNow=Date.now()){
const recent=s.training_throughput;
const numeric=v=>typeof v==='number'&&Number.isFinite(v)&&v>=0;
const ended=recent&&typeof recent.window_end_utc==='string'?Date.parse(recent.window_end_utc):NaN;
if(recent&&recent.state==='measured'&&numeric(recent.bytes_per_s)&&numeric(recent.window_s)&&recent.window_s>=30&&recent.window_s<=61&&numeric(recent.accepted_byte_targets)&&Number.isFinite(ended)&&serverNow-ended>=-3000&&serverNow-ended<=20000){
samples.length=0;
show('trainRate',fmt.format(Math.round(recent.bytes_per_s))+' / s');
show('rateWindow',fmt.format(recent.accepted_byte_targets)+' bytes in the last '+Math.round(recent.window_s)+' seconds · measured by the coordinator');
return
}
const now=performance.now();
if(samples.length&&(s.tokens<samples[samples.length-1].tokens||now-samples[samples.length-1].time>30000))samples.length=0;
samples.push({time:now,tokens:s.tokens});
while(samples.length>1&&now-samples[0].time>65000)samples.shift();
const first=samples[0],seconds=(now-first.time)/1000;
if(seconds<30){show('trainRate','Measuring…');show('rateWindow','Collecting at least 30 seconds of coordinator counter samples.');return}
show('trainRate',fmt.format(Math.round((s.tokens-first.tokens)/seconds))+' / s');
show('rateWindow',fmt.format(s.tokens-first.tokens)+' bytes in the last '+Math.round(seconds)+' seconds · measured on this page');
}
function usefulCard(g){if(!g||typeof g!=='object'){show('usefulNow','');return}
const parts=[fmt.format((number(g.passed)||0)+(number(g.refused)||0))+' updates tested',fmt.format(number(g.refused)||0)+' refused'];
if(typeof g.recent_improved==='number'&&Number.isFinite(g.recent_improved)&&number(g.recent_tested))parts.push(Math.round(100*g.recent_improved)+'% of the last '+fmt.format(g.recent_tested)+' made held-out text better');
show('usefulNow',parts.join(' · '))}
function networkCard(n){if(!n||typeof n!=='object'){show('inferRate','Unavailable');show('inferDetail','');return}
const speed=number(n.bytes_per_s_last_hour);show('inferRate',!speed?(n.ready?'Ready':'Waiting for workers'):speed.toFixed(2)+' bytes / s');
show('inferDetail',[fmt.format(number(n.covered_blocks)||0)+' of '+fmt.format(number(n.blocks)||0)+' blocks have a worker online',fmt.format(number(n.online_workers)||0)+' workers serving',fmt.format(number(n.hops_network)||0)+' hops run on workers',fmt.format(number(n.hops_coordinator)||0)+' on the coordinator',fmt.format(number(n.audits)||0)+' spot-checked',fmt.format(number(n.audit_failures)||0)+' wrong'].join(' · '))}
function draw(s,serverNow=Date.now()){
rate(s,serverNow);workers(s,serverNow);networkCard(s.net_inference);usefulCard(s.gate);
show('updates',fmt.format(s.updates));show('bytes',fmt.format(s.tokens));
show('workers',new Set((s.active_leases||[]).map(v=>v.worker)).size);
const ev=s.last_eval||{};show('bpb',number(ev.ar_bits_per_byte)===null?'Not evaluated':Number(ev.ar_bits_per_byte).toFixed(3));
const values=Array.isArray(s.block_updates)?s.block_updates:[],max=Math.max(1,...values);
$('blocks').replaceChildren();
values.forEach((v,i)=>{const row=document.createElement('div');row.className='barrow';const label=document.createElement('div');label.className='barlabel';const name=document.createElement('span');name.textContent='Block '+(i+1);const total=document.createElement('span');total.textContent=fmt.format(v)+' updates';label.append(name,total);const track=document.createElement('div');track.className='track';const fill=document.createElement('div');fill.className='fill';fill.style.width=(100*v/max)+'%';track.append(fill);row.append(label,track);$('blocks').append(row)});
const h=(s.eval_history||[]).filter(p=>number(p.ar_bpb)!==null&&number(p.updates)!==null);
$('chart').replaceChildren();
if(h.length){const ns='http://www.w3.org/2000/svg',svg=document.createElementNS(ns,'svg');svg.setAttribute('viewBox','0 0 420 160');svg.setAttribute('role','img');svg.setAttribute('aria-label','Evaluation bits per byte: '+h.map(p=>'update '+p.updates+': '+p.ar_bpb).join('; '));
const lo=Math.min(...h.map(p=>p.ar_bpb)),hi=Math.max(...h.map(p=>p.ar_bpb)),x0=h[0].updates,x1=h[h.length-1].updates;
const points=h.map((p,i)=>[h.length===1?210:15+390*(p.updates-x0)/Math.max(1,x1-x0),140-120*(p.ar_bpb-lo)/Math.max(.1,hi-lo)]);
const line=document.createElementNS(ns,'polyline');line.setAttribute('points',points.map(p=>p.join(',')).join(' '));line.setAttribute('fill','none');line.setAttribute('stroke','#86e9bc');line.setAttribute('stroke-width','3');svg.append(line);
points.forEach((p,i)=>{const dot=document.createElementNS(ns,'circle');dot.setAttribute('cx',p[0]);dot.setAttribute('cy',p[1]);dot.setAttribute('r','4');dot.setAttribute('fill','#8ebfff');const title=document.createElementNS(ns,'title');title.textContent='Update '+h[i].updates+': '+h[i].ar_bpb+' bits/byte';dot.append(title);svg.append(dot)});$('chart').append(svg);
show('firstEval','Update '+fmt.format(h[0].updates)+' · '+h[0].ar_bpb+' bpb');show('lastEval','Update '+fmt.format(h[h.length-1].updates)+' · '+h[h.length-1].ar_bpb+' bpb');
}else{show('chart','Awaiting the first completed evaluation.');show('firstEval','—');show('lastEval','—')}
show('evalTime',ev.t?'Last evaluation: '+ev.t+' · update '+fmt.format(ev.updates):'No completed evaluation reported.');
show('genQuality',[ev.chain_gen_bits,ev.top_gen_bits,ev.real_bits].every(v=>typeof v==='number'&&Number.isFinite(v))?'Generated text scored against the statistics of real text, in bits per byte: every block '+ev.chain_gen_bits.toFixed(2)+', top block '+ev.top_gen_bits.toFixed(2)+', real text '+ev.real_bits.toFixed(2)+'. Closer to real text is better.':'');
show('sample',typeof ev.sample==='string'?ev.sample:'No sample available.');
const cfg=s.cfg||{},tags=[fmt.format(s.params)+' parameters',cfg.blocks+' blocks',cfg.experts+' experts / block','Context: '+cfg.ctx+' bytes',s.version||''];
$('metadata').replaceChildren();tags.forEach(t=>{const el=document.createElement('span');el.textContent=t;$('metadata').append(el)});
show('source','Reported dataset: '+((s.data||{}).source||'Unavailable'));
}
async function refresh(){if(busy)return;busy=true;$('refresh').disabled=true;try{
const r=await fetch('./status',{cache:'no-store',signal:AbortSignal.timeout(10000)});if(!r.ok)throw new Error('Status endpoint returned HTTP '+r.status);
const s=await r.json();if(!Number.isFinite(s.updates)||!Number.isFinite(s.tokens)||!Array.isArray(s.block_updates))throw new Error('Unexpected metrics format');
lastSuccess=Date.now();draw(s,Date.parse(r.headers.get('Date'))||Date.now());show('connection','Status endpoint connected');$('connection').classList.remove('warn');show('updated','Last received '+new Date(lastSuccess).toLocaleTimeString()+' · refreshes every 15 seconds');show('error','');
}catch(e){show('trainRate','Unavailable');show('rateWindow','Waiting for fresh coordinator samples.');samples.length=0;show('connection',lastSuccess?'Connection lost · showing last received metrics':'Metrics unavailable');$('connection').classList.add('warn');show('error','Could not refresh. '+(lastSuccess?'The displayed values may be stale. ':'')+'Try Refresh shortly.');}finally{busy=false;$('refresh').disabled=false}}
workers({workers:{},active_leases:[]},Date.now(),false);setInterval(()=>{if(latestWorkerSnapshot){const elapsed=Date.now()-latestWorkerReceived;const snapshot={...latestWorkerSnapshot,active_leases:(latestWorkerSnapshot.active_leases||[]).map(l=>({...l,age_s:Number(l.age_s)+elapsed/1000}))};workers(snapshot,latestWorkerClock+elapsed,false)}},15000);checkOwner();setInterval(checkOwner,60000);$('refresh').addEventListener('click',()=>{refresh();checkOwner()});document.addEventListener('visibilitychange',()=>{if(!document.hidden)refresh()});refresh();setInterval(()=>{if(!document.hidden)refresh()},15000);
</script></body></html>