|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<title>Bit Transformer Dashboard</title> |
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> |
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> |
|
|
</head> |
|
|
<body> |
|
|
<h1>Bit Transformer Dashboard</h1> |
|
|
<div class="container"> |
|
|
<section> |
|
|
<h2>Initialize Model</h2> |
|
|
<form id="initForm"> |
|
|
d_model: <input type="number" name="d_model" value="{{ defaults.d_model }}" title="Model width (default {{ defaults.d_model }})"><br> |
|
|
nhead: <input type="number" name="nhead" value="{{ defaults.nhead }}" title="Attention heads (default {{ defaults.nhead }})"><br> |
|
|
num_layers: <input type="number" name="num_layers" value="{{ defaults.num_layers }}" title="Transformer layers (default {{ defaults.num_layers }})"><br> |
|
|
dim_feedforward: <input type="number" name="dim_feedforward" value="{{ defaults.dim_feedforward }}" title="Feedforward dim (default {{ defaults.dim_feedforward }})"><br> |
|
|
max_seq_len: <input type="number" name="max_seq_len" value="{{ defaults.max_seq_len }}" title="Max sequence length (default {{ defaults.max_seq_len }})"><br> |
|
|
chunk_size: <input type="number" name="chunk_size" title="Chunked attention size"><br> |
|
|
overlap: <input type="number" name="overlap" value="{{ defaults.overlap }}" title="Sliding window overlap"><br> |
|
|
Reversible: <input type="checkbox" name="reversible" id="reversible_box" title="Use reversible layers (default {{ defaults.reversible }})"><br> |
|
|
Gradient Checkpointing: <input type="checkbox" name="use_checkpoint" id="checkpoint_box" checked title="Enable gradient checkpointing (default {{ defaults.use_checkpoint }})"><br> |
|
|
act_threshold: <input type="number" step="0.01" name="act_threshold" value="{{ defaults.act_threshold }}" title="ACT halt threshold (default {{ defaults.act_threshold }})"><br> |
|
|
c_floor: <input type="number" step="0.01" name="c_floor" value="{{ c_floor }}" title="Complexity floor"><br> |
|
|
s_floor: <input type="number" step="0.01" name="s_floor" value="{{ s_floor }}" title="Symbiosis floor"><br> |
|
|
<button type="submit">Init</button> |
|
|
</form> |
|
|
</section> |
|
|
<section> |
|
|
<h2>Train Step</h2> |
|
|
<form id="trainForm"> |
|
|
Bits (e.g. 0 1 0 1): <input type="text" name="bits" value="0 1 0 1"><br> |
|
|
Upload file: <input type="file" id="train_file"><br> |
|
|
<button type="submit">Train</button> |
|
|
</form> |
|
|
<label>Load sample dataset: |
|
|
<select id="datasetSelect"> |
|
|
<option value="">--Select--</option> |
|
|
<option value="wikitext2_train">Wikitext-2 (train)</option> |
|
|
<option value="wikitext2_validation">Wikitext-2 (validation)</option> |
|
|
</select> |
|
|
</label> |
|
|
<p id="trainOut"></p> |
|
|
</section> |
|
|
<section> |
|
|
<h2>Scale Up</h2> |
|
|
Width Mult: <input type="number" step="0.1" id="width_mult" value="1.0"><br> |
|
|
<button id="scaleBtn">Scale Model</button> |
|
|
</section> |
|
|
<section> |
|
|
<h2>Collapse Submodel</h2> |
|
|
<form id="collapseForm"> |
|
|
Cluster Bits (JSON array of arrays):<br> |
|
|
<textarea name="clusters" rows="3" cols="40">[[0,1,0,1],[1,1,0,0]]</textarea><br> |
|
|
Target Params (JSON):<br> |
|
|
<textarea name="params" rows="3" cols="40">{"d_model":32,"nhead":4,"num_layers":1,"dim_feedforward":64,"max_seq_len":16}</textarea><br> |
|
|
Width Scale: <input type="number" step="0.1" id="width_scale" value="1.0"><br> |
|
|
<button type="submit">Collapse</button> |
|
|
</form> |
|
|
</section> |
|
|
<section> |
|
|
<h2>Inference</h2> |
|
|
<form id="inferForm"> |
|
|
Bits: <input type="text" name="bits" value="0 1 0 1"><br> |
|
|
Upload file: <input type="file" id="infer_file"><br> |
|
|
<button type="submit">Infer</button> |
|
|
</form> |
|
|
<pre id="inferOut"></pre> |
|
|
</section> |
|
|
<section> |
|
|
<h2>Long Inference</h2> |
|
|
<form id="inferLongForm"> |
|
|
Bits: <input type="text" name="bits" value="0 1 0 1"><br> |
|
|
ctx_bits: <input type="number" name="ctx_bits" value="4096"><br> |
|
|
overlap: <input type="number" name="overlap" value="256"><br> |
|
|
<button type="submit">Infer Long</button> |
|
|
</form> |
|
|
<pre id="inferLongOut"></pre> |
|
|
</section> |
|
|
<section> |
|
|
<h2>Text Inference</h2> |
|
|
<form id="textInferForm"> |
|
|
Text: <input type="text" name="text" value="hello"><br> |
|
|
<button type="submit">Infer Text</button> |
|
|
</form> |
|
|
<pre id="textInferOut"></pre> |
|
|
</section> |
|
|
<section> |
|
|
<h2>λ Weights</h2> |
|
|
<form id="lambdaForm"> |
|
|
λ<sub>K</sub>: <input type="range" min="0" max="2" step="0.1" id="lambda_K" oninput="lambda_K_val.innerText=value"><span id="lambda_K_val"></span><br> |
|
|
λ<sub>C</sub>: <input type="range" min="0" max="2" step="0.1" id="lambda_C" oninput="lambda_C_val.innerText=value"><span id="lambda_C_val"></span><br> |
|
|
λ<sub>S</sub>: <input type="range" min="0" max="2" step="0.1" id="lambda_S" oninput="lambda_S_val.innerText=value"><span id="lambda_S_val"></span><br> |
|
|
<button type="submit">Update</button> |
|
|
</form> |
|
|
</section> |
|
|
<section> |
|
|
<h2>Diffusion LM</h2> |
|
|
<label><input type="checkbox" id="diffusion_box"> Enable Diffusion Mode</label> |
|
|
</section> |
|
|
<section> |
|
|
<h2>GPU Acceleration</h2> |
|
|
<label><input type="checkbox" id="gpu_box"> Enable FSDP & CUDA</label> |
|
|
</section> |
|
|
<section> |
|
|
<h2>Enable Compression</h2> |
|
|
<label><input type="checkbox" id="compression_box"> Compress I/O</label> |
|
|
<p>Ratio: <span id="comp_ratio">1.0</span></p> |
|
|
</section> |
|
|
<section> |
|
|
<h2>Quantization Aware Training</h2> |
|
|
<label><input type="checkbox" id="qat_box"> Enable 4-bit QAT</label> |
|
|
</section> |
|
|
<section> |
|
|
<h2>Model Status</h2> |
|
|
<pre id="statusOut"></pre> |
|
|
</section> |
|
|
<section> |
|
|
<h2>Telemetry</h2> |
|
|
<canvas id="metricChart" width="600" height="300"></canvas> |
|
|
</section> |
|
|
<section> |
|
|
<h2>Hugging Face Checkpoints</h2> |
|
|
Repo ID: <input type="text" id="hf_repo"><br> |
|
|
Token: <input type="password" id="hf_token" placeholder="optional"><br> |
|
|
<button id="uploadBtn">Upload weights</button> |
|
|
<button id="downloadBtn">Download weights</button> |
|
|
<p id="hfStatus"></p> |
|
|
</section> |
|
|
|
|
|
<script> |
|
|
async function postJSON(url, data){ |
|
|
const resp = await fetch(url, {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(data)}); |
|
|
return resp.json(); |
|
|
} |
|
|
|
|
|
async function pollJob(id){ |
|
|
while(true){ |
|
|
const job = await fetch(`/job/${id}`).then(r=>r.json()); |
|
|
if(job.status === 'completed') return job.result; |
|
|
if(job.status === 'error') throw job.error || 'Job failed'; |
|
|
await new Promise(r=>setTimeout(r, 1000)); |
|
|
} |
|
|
} |
|
|
|
|
|
function loadInitParams(){ |
|
|
const saved = JSON.parse(localStorage.getItem('init_params')||'{}'); |
|
|
const form = document.getElementById('initForm'); |
|
|
for(const [k,v] of Object.entries(saved)){ |
|
|
const el = form.elements[k]; |
|
|
if(!el) continue; |
|
|
if(el.type === 'checkbox') el.checked = v; else el.value = v; |
|
|
} |
|
|
} |
|
|
loadInitParams(); |
|
|
|
|
|
function byteArrayToBits(arr){ |
|
|
const bits=[]; |
|
|
for(const b of arr){ |
|
|
for(let i=7;i>=0;i--) bits.push((b>>i)&1); |
|
|
} |
|
|
return bits; |
|
|
} |
|
|
|
|
|
let trainFileBits=null, inferFileBits=null, datasetBits=null; |
|
|
|
|
|
async function fileToBits(file){ |
|
|
if(file.type.startsWith('text')){ |
|
|
const text = await file.text(); |
|
|
const res = await postJSON('/text_to_bits', {text}); |
|
|
return res.bits; |
|
|
} |
|
|
const buf = await file.arrayBuffer(); |
|
|
return byteArrayToBits(new Uint8Array(buf)); |
|
|
} |
|
|
|
|
|
let metricChart; |
|
|
async function initChart(){ |
|
|
const data = await fetch('/metrics').then(r=>r.json()); |
|
|
const labels = data.negentropy.map((_,i)=>i); |
|
|
const ctx = document.getElementById('metricChart').getContext('2d'); |
|
|
metricChart = new Chart(ctx, { |
|
|
type:'line', |
|
|
data:{ |
|
|
labels:labels, |
|
|
datasets:[ |
|
|
{label:'Negentropy', data:data.negentropy, borderColor:'blue', fill:false}, |
|
|
{label:'LZ Complexity', data:data.lz_complexity, borderColor:'orange', fill:false}, |
|
|
{label:'Symbiosis', data:data.symbiosis, borderColor:'green', fill:false} |
|
|
] |
|
|
}, |
|
|
options:{responsive:false, interaction:{mode:'index', intersect:false}} |
|
|
}); |
|
|
} |
|
|
|
|
|
async function updateChart(){ |
|
|
const data = await fetch('/metrics').then(r=>r.json()); |
|
|
const labels = data.negentropy.map((_,i)=>i); |
|
|
metricChart.data.labels = labels; |
|
|
metricChart.data.datasets[0].data = data.negentropy; |
|
|
metricChart.data.datasets[1].data = data.lz_complexity; |
|
|
metricChart.data.datasets[2].data = data.symbiosis; |
|
|
metricChart.update(); |
|
|
} |
|
|
|
|
|
initChart(); |
|
|
setInterval(updateChart, 2000); |
|
|
|
|
|
async function refreshStatus(){ |
|
|
const [s, c] = await Promise.all([fetch('/status'), fetch('/model_config')]); |
|
|
const status = await s.json(); |
|
|
const config = await c.json(); |
|
|
document.getElementById('statusOut').innerText = JSON.stringify({...status, ...config}, null, 2); |
|
|
} |
|
|
|
|
|
document.getElementById('initForm').addEventListener('submit', async (e)=>{ |
|
|
e.preventDefault(); |
|
|
const fd = new FormData(e.target); |
|
|
const obj = Object.fromEntries(fd.entries()); |
|
|
const ints = ['d_model','nhead','num_layers','dim_feedforward','max_seq_len','chunk_size','overlap']; |
|
|
ints.forEach(k=>{ if(obj[k]===''){ delete obj[k]; } else obj[k]=parseInt(obj[k]); }); |
|
|
obj.reversible = document.getElementById('reversible_box').checked; |
|
|
obj.use_checkpoint = document.getElementById('checkpoint_box').checked; |
|
|
obj.act_threshold = parseFloat(obj.act_threshold); |
|
|
const floors = {c_floor: parseFloat(obj.c_floor), s_floor: parseFloat(obj.s_floor)}; |
|
|
delete obj.c_floor; delete obj.s_floor; |
|
|
await postJSON('/init', obj); |
|
|
await postJSON('/config/telemetry', floors); |
|
|
localStorage.setItem('init_params', JSON.stringify({...obj, ...floors})); |
|
|
refreshStatus(); |
|
|
updateChart(); |
|
|
}); |
|
|
|
|
|
document.getElementById('trainForm').addEventListener('submit', async (e)=>{ |
|
|
e.preventDefault(); |
|
|
const form = e.target; |
|
|
let payload; |
|
|
if(trainFileBits){ |
|
|
payload = trainFileBits; |
|
|
} else if(datasetBits){ |
|
|
payload = datasetBits; |
|
|
} else { |
|
|
payload = [form.bits.value.trim().split(/\s+/).map(Number)]; |
|
|
} |
|
|
for(const el of form.elements) el.disabled = true; |
|
|
const out = document.getElementById('trainOut'); |
|
|
out.innerText = '⏳'; |
|
|
try{ |
|
|
const job = await postJSON('/train', {bits: payload}); |
|
|
const res = await pollJob(job.job_id); |
|
|
out.innerText = 'Loss: '+res.loss.toFixed(4); |
|
|
if(res.ratio !== undefined){ |
|
|
document.getElementById('comp_ratio').innerText = res.ratio.toFixed(2); |
|
|
} |
|
|
} catch(err){ |
|
|
out.innerText = 'Error'; |
|
|
alert(err); |
|
|
} finally { |
|
|
for(const el of form.elements) el.disabled = false; |
|
|
refreshStatus(); |
|
|
updateChart(); |
|
|
} |
|
|
}); |
|
|
|
|
|
document.getElementById('train_file').addEventListener('change', async (e)=>{ |
|
|
const f = e.target.files[0]; |
|
|
if(!f) return; |
|
|
const bits = await fileToBits(f); |
|
|
trainFileBits = [bits]; |
|
|
datasetBits = null; |
|
|
document.querySelector('#trainForm input[name="bits"]').value = bits.slice(0,64).join(' '); |
|
|
}); |
|
|
|
|
|
document.querySelector('#trainForm input[name="bits"]').addEventListener('input', ()=>{ |
|
|
trainFileBits = null; |
|
|
datasetBits = null; |
|
|
}); |
|
|
|
|
|
document.getElementById('scaleBtn').addEventListener('click', async ()=>{ |
|
|
const btn = document.getElementById('scaleBtn'); |
|
|
const input = document.getElementById('width_mult'); |
|
|
const mult = parseFloat(input.value); |
|
|
btn.disabled = true; input.disabled = true; |
|
|
const original = btn.innerText; btn.innerText = '⏳'; |
|
|
try{ |
|
|
const job = await postJSON('/scale_up', {width_mult: mult}); |
|
|
await pollJob(job.job_id); |
|
|
} catch(err){ |
|
|
alert(err); |
|
|
} finally { |
|
|
btn.innerText = original; |
|
|
btn.disabled = false; input.disabled = false; |
|
|
refreshStatus(); |
|
|
updateChart(); |
|
|
} |
|
|
}); |
|
|
|
|
|
document.getElementById('collapseForm').addEventListener('submit', async (e)=>{ |
|
|
e.preventDefault(); |
|
|
const form = e.target; |
|
|
const btn = form.querySelector('button'); |
|
|
for(const el of form.elements) el.disabled = true; |
|
|
const clusters = JSON.parse(form.clusters.value); |
|
|
const params = JSON.parse(form.params.value); |
|
|
const w = parseFloat(document.getElementById('width_scale').value); |
|
|
const original = btn.innerText; btn.innerText = '⏳'; |
|
|
try{ |
|
|
const job = await postJSON('/collapse', {clusters: clusters, params: params, width_scale: w}); |
|
|
await pollJob(job.job_id); |
|
|
} catch(err){ |
|
|
alert(err); |
|
|
} finally { |
|
|
btn.innerText = original; |
|
|
for(const el of form.elements) el.disabled = false; |
|
|
refreshStatus(); |
|
|
updateChart(); |
|
|
} |
|
|
}); |
|
|
|
|
|
document.getElementById('inferForm').addEventListener('submit', async (e)=>{ |
|
|
e.preventDefault(); |
|
|
let bits; |
|
|
if(inferFileBits){ |
|
|
bits = inferFileBits; |
|
|
} else if(datasetBits){ |
|
|
bits = [datasetBits[0]]; |
|
|
} else { |
|
|
bits = [e.target.bits.value.trim().split(/\s+/).map(Number)]; |
|
|
} |
|
|
const res = await postJSON('/infer', {bits}); |
|
|
if(res.error){ |
|
|
alert(res.error + '\n' + (res.suggestion||'')); |
|
|
} else { |
|
|
document.getElementById('inferOut').innerText = JSON.stringify(res, null, 2); |
|
|
if(res.ratio !== undefined){ |
|
|
document.getElementById('comp_ratio').innerText = res.ratio.toFixed(2); |
|
|
} |
|
|
} |
|
|
refreshStatus(); |
|
|
updateChart(); |
|
|
}); |
|
|
|
|
|
document.getElementById('infer_file').addEventListener('change', async (e)=>{ |
|
|
const f = e.target.files[0]; |
|
|
if(!f) return; |
|
|
const bits = await fileToBits(f); |
|
|
inferFileBits = [bits]; |
|
|
datasetBits = null; |
|
|
document.querySelector('#inferForm input[name="bits"]').value = bits.slice(0,64).join(' '); |
|
|
}); |
|
|
|
|
|
document.querySelector('#inferForm input[name="bits"]').addEventListener('input', ()=>{ |
|
|
inferFileBits = null; |
|
|
datasetBits = null; |
|
|
}); |
|
|
|
|
|
document.getElementById('datasetSelect').addEventListener('change', async (e)=>{ |
|
|
const val = e.target.value; |
|
|
trainFileBits = null; |
|
|
inferFileBits = null; |
|
|
if(!val){ datasetBits = null; return; } |
|
|
const [name, split] = val.split('_'); |
|
|
const resp = await fetch(`/dataset?name=${name}&split=${split}&size=4&seq_len=64`); |
|
|
const data = await resp.json(); |
|
|
datasetBits = data.bits; |
|
|
const preview = data.bits[0].slice(0,64).join(' '); |
|
|
document.querySelector('#trainForm input[name="bits"]').value = preview; |
|
|
document.querySelector('#inferForm input[name="bits"]').value = preview; |
|
|
}); |
|
|
|
|
|
document.getElementById('inferLongForm').addEventListener('submit', async (e)=>{ |
|
|
e.preventDefault(); |
|
|
const bits = e.target.bits.value.trim().split(/\s+/).map(Number); |
|
|
const ctx = parseInt(e.target.ctx_bits.value); |
|
|
const ov = parseInt(e.target.overlap.value); |
|
|
const res = await postJSON('/infer_long', {bits: bits, ctx_bits: ctx, overlap: ov}); |
|
|
document.getElementById('inferLongOut').innerText = JSON.stringify(res, null, 2); |
|
|
refreshStatus(); |
|
|
updateChart(); |
|
|
}); |
|
|
|
|
|
document.getElementById('textInferForm').addEventListener('submit', async (e)=>{ |
|
|
e.preventDefault(); |
|
|
const text = e.target.text.value; |
|
|
const res = await postJSON('/infer_text', {text:text}); |
|
|
document.getElementById('textInferOut').innerText = JSON.stringify(res, null, 2); |
|
|
refreshStatus(); |
|
|
updateChart(); |
|
|
}); |
|
|
|
|
|
async function loadLambdas(){ |
|
|
const resp = await fetch('/lambdas'); |
|
|
const vals = await resp.json(); |
|
|
for(const k of ['lambda_K','lambda_C','lambda_S']){ |
|
|
document.getElementById(k).value = vals[k]; |
|
|
document.getElementById(k+"_val").innerText = vals[k]; |
|
|
} |
|
|
} |
|
|
|
|
|
document.getElementById('lambdaForm').addEventListener('submit', async (e)=>{ |
|
|
e.preventDefault(); |
|
|
const data = { |
|
|
lambda_K: parseFloat(document.getElementById('lambda_K').value), |
|
|
lambda_C: parseFloat(document.getElementById('lambda_C').value), |
|
|
lambda_S: parseFloat(document.getElementById('lambda_S').value), |
|
|
}; |
|
|
await postJSON('/lambdas', data); |
|
|
for(const k in data){ |
|
|
document.getElementById(k+"_val").innerText = data[k]; |
|
|
} |
|
|
refreshStatus(); |
|
|
}); |
|
|
|
|
|
loadLambdas(); |
|
|
|
|
|
function restoreToggle(id,key,endpoint,field){ |
|
|
const box = document.getElementById(id); |
|
|
const saved = localStorage.getItem(key); |
|
|
if(saved !== null){ box.checked = saved === 'true'; postJSON(endpoint,{[field]: box.checked}); } |
|
|
box.addEventListener('change', async (e)=>{ |
|
|
await postJSON(endpoint, {[field]: e.target.checked}); |
|
|
localStorage.setItem(key, e.target.checked); |
|
|
refreshStatus(); |
|
|
}); |
|
|
} |
|
|
|
|
|
restoreToggle('diffusion_box','diffusion','/diffusion','diffusion'); |
|
|
restoreToggle('gpu_box','use_gpu','/gpu','use_gpu'); |
|
|
restoreToggle('compression_box','compression','/compression','compression'); |
|
|
restoreToggle('qat_box','qat','/qat','qat'); |
|
|
|
|
|
document.getElementById('uploadBtn').addEventListener('click', async ()=>{ |
|
|
const repo = document.getElementById('hf_repo').value; |
|
|
const token = document.getElementById('hf_token').value; |
|
|
const res = await postJSON('/save_checkpoint', {repo_id: repo, token: token||undefined}); |
|
|
document.getElementById('hfStatus').innerText = res.status || res.error; |
|
|
}); |
|
|
|
|
|
document.getElementById('downloadBtn').addEventListener('click', async ()=>{ |
|
|
const repo = document.getElementById('hf_repo').value; |
|
|
const token = document.getElementById('hf_token').value; |
|
|
const res = await postJSON('/download_checkpoint', {repo_id: repo, token: token||undefined}); |
|
|
document.getElementById('hfStatus').innerText = res.status || res.error; |
|
|
refreshStatus(); |
|
|
updateChart(); |
|
|
}); |
|
|
|
|
|
refreshStatus(); |
|
|
</script> |
|
|
</div> |
|
|
</body> |
|
|
</html> |
|
|
|
|
|
|