Updated slider

This commit is contained in:
Donato Capitella
2025-08-09 15:58:29 +01:00
parent e49efe221e
commit 3e4a3e7f9e
+134 -47
View File
@@ -92,6 +92,75 @@
align-items: center; align-items: center;
} }
.range-wrap {
position: relative;
height: 30px;
}
.range-wrap input[type="range"] {
position: absolute;
inset: 0;
width: 100%;
margin: 0;
background: transparent;
pointer-events: none;
appearance: none;
}
.range-wrap input[type="range"]::-webkit-slider-thumb {
pointer-events: auto;
appearance: none;
height: 16px;
width: 16px;
border-radius: 50%;
background: var(--accent);
border: 2px solid #fff;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
cursor: pointer;
transition: background 0.2s, transform 0.2s;
}
.range-wrap input[type="range"]::-webkit-slider-thumb:hover {
background: #0451cc;
transform: scale(1.1);
}
.range-wrap input[type="range"]::-moz-range-thumb {
pointer-events: auto;
height: 16px;
width: 16px;
border-radius: 50%;
background: var(--accent);
border: 2px solid #fff;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
cursor: pointer;
transition: background 0.2s, transform 0.2s;
}
.range-wrap input[type="range"]::-moz-range-thumb:hover {
background: #0451cc;
transform: scale(1.1);
}
.range-track {
position: absolute;
top: 50%;
height: 6px;
width: 100%;
transform: translateY(-50%);
border-radius: 999px;
background: #e5e5e5;
}
.range-values {
margin-top: 4px;
font-size: 12px;
color: var(--muted);
text-align: center;
}
input[type="range"] { input[type="range"] {
width: 100%; width: 100%;
} }
@@ -311,13 +380,22 @@
</div> </div>
<div> <div>
<label>Model params</label> <label>Model params</label>
<div class="slider">
<input id="size" type="range" step="0.01" min="0" max="100" value="0" list="tickmarks"> <div class="range-wrap" id="sizeRange">
<input id="sizeOut" type="number" step="0.01"> <input type="range" id="sizeLo" step="1">
<input type="range" id="sizeHi" step="1">
<div class="range-track" id="sizeTrack"></div>
</div>
<div class="ticks">
<span>4B</span><span>50B</span><span>96B</span><span>143B</span><span>189B</span><span>235B</span>
</div>
<div class="range-values">
<span id="sizeLoVal">4B</span> <span id="sizeHiVal">235B</span>
</div> </div>
<div class="ticks" id="tickLabels"></div>
<datalist id="tickmarks"></datalist>
</div> </div>
<div> <div>
<label for="faMode">Flash Attention</label> <label for="faMode">Flash Attention</label>
<select id="faMode"> <select id="faMode">
@@ -362,24 +440,27 @@
<script> <script>
(async function () { (async function () {
// dynamic tie threshold: within K sigmas of best, with a small absolute floor const K_SIGMA = 1.0;
const K_SIGMA = 1.0; // 1σ; set 1.96 for ~95% CI const MIN_TOL = 0.25;
const MIN_TOL = 0.25; // tokens/s floor if stds are tiny/missing
const res = await fetch('results.json'); const data = await res.json(); const res = await fetch('results.json');
const data = await res.json();
const allRuns = data.runs || []; const allRuns = data.runs || [];
const perfRuns = allRuns.filter(r => r.test === 'pp512' || r.test === 'tg128'); const perfRuns = allRuns.filter(r => r.test === 'pp512' || r.test === 'tg128');
const metaLine = document.getElementById('meta-line'); const metaLine = document.getElementById('meta-line');
const search = document.getElementById('search'); const search = document.getElementById('search');
const quantSel = document.getElementById('quant'); const quantSel = document.getElementById('quant');
const size = document.getElementById('size');
const sizeOut = document.getElementById('sizeOut');
const ticks = document.getElementById('tickmarks');
const tickLabels = document.getElementById('tickLabels');
const colPicker = document.getElementById('columns'); const colPicker = document.getElementById('columns');
const faMode = document.getElementById('faMode'); const faMode = document.getElementById('faMode');
// NEW dual slider elements
const sizeLo = document.getElementById('sizeLo');
const sizeHi = document.getElementById('sizeHi');
const sizeTrack = document.getElementById('sizeTrack');
const sizeLoVal = document.getElementById('sizeLoVal');
const sizeHiVal = document.getElementById('sizeHiVal');
metaLine.textContent = `${data.meta?.os_kernel || ''} — llama.cpp ${(data.meta?.llamacpp_builds || []).map(b => b.hash).join(', ') || 'unknown'} — generated ${data.meta?.generated_at || ''}`; metaLine.textContent = `${data.meta?.os_kernel || ''} — llama.cpp ${(data.meta?.llamacpp_builds || []).map(b => b.hash).join(', ') || 'unknown'} — generated ${data.meta?.generated_at || ''}`;
const models = [...new Set(perfRuns.map(r => r.model_clean || r.model))].sort((a, b) => a.localeCompare(b)); const models = [...new Set(perfRuns.map(r => r.model_clean || r.model))].sort((a, b) => a.localeCompare(b));
@@ -392,7 +473,6 @@
if (!byModel[name].quant && r.quant) byModel[name].quant = r.quant; if (!byModel[name].quant && r.quant) byModel[name].quant = r.quant;
if (byModel[name].sizeB == null) byModel[name].sizeB = (r.name_params_b ?? r.params_b); if (byModel[name].sizeB == null) byModel[name].sizeB = (r.name_params_b ?? r.params_b);
} }
// include error-only logs so we can show ⚠️
for (const r of allRuns) { for (const r of allRuns) {
if (r.test || !r.error) continue; if (r.test || !r.error) continue;
const name = r.model_clean || r.model; if (!byModel[name]) continue; const name = r.model_clean || r.model; if (!byModel[name]) continue;
@@ -420,39 +500,63 @@
const quants = [...new Set(Object.values(byModel).map(m => (m.quant || 'Unknown').toUpperCase()))].sort(); const quants = [...new Set(Object.values(byModel).map(m => (m.quant || 'Unknown').toUpperCase()))].sort();
quants.forEach(q => { const o = document.createElement('option'); o.value = (q === 'UNKNOWN' ? '' : q); o.textContent = q; quantSel.appendChild(o); }); quants.forEach(q => { const o = document.createElement('option'); o.value = (q === 'UNKNOWN' ? '' : q); o.textContent = q; quantSel.appendChild(o); });
// --- Dual range slider setup ---
const sizes = Object.values(byModel).map(m => m.sizeB).filter(v => typeof v === 'number').sort((a, b) => a - b); const sizes = Object.values(byModel).map(m => m.sizeB).filter(v => typeof v === 'number').sort((a, b) => a - b);
const minB = sizes[0] ?? 0, maxB = sizes[sizes.length - 1] ?? 100; const MIN_B = sizes[0] ?? 0;
size.min = String(minB); size.max = String(maxB); size.step = "0.01"; size.value = String(minB); sizeOut.value = String(minB); const MAX_B = sizes[sizes.length - 1] ?? 100;
const marks = 6; ticks.innerHTML = ''; tickLabels.innerHTML = ''; [sizeLo, sizeHi].forEach(inp => { inp.min = MIN_B; inp.max = MAX_B; inp.step = 1; });
for (let i = 0; i < marks; i++) { sizeLo.value = MIN_B;
const v = (minB + (i * (maxB - minB) / (marks - 1))); sizeHi.value = MAX_B;
const opt = document.createElement('option'); opt.value = v.toFixed(2); ticks.appendChild(opt); const filters = { sizeLo: MIN_B, sizeHi: MAX_B };
const lab = document.createElement('span'); lab.textContent = `${v.toFixed(0)}B`; tickLabels.appendChild(lab);
function fmtB(n) { return `${Number(n).toFixed(0)}B`; }
function clampRange() {
if (+sizeLo.value > +sizeHi.value) {
const active = document.activeElement === sizeLo ? sizeLo : sizeHi;
if (active === sizeLo) sizeHi.value = sizeLo.value;
else sizeLo.value = sizeHi.value;
}
} }
function paintTrack() {
const a = (+sizeLo.value - MIN_B) / (MAX_B - MIN_B) * 100;
const b = (+sizeHi.value - MIN_B) / (MAX_B - MIN_B) * 100;
sizeTrack.style.background =
`linear-gradient(to right,
#e5e5e5 ${a}%,
var(--accent) ${a}%,
var(--accent) ${b}%,
#e5e5e5 ${b}%)`;
}
function updateSizeUI(pushRender = true) {
clampRange();
sizeLoVal.textContent = fmtB(sizeLo.value);
sizeHiVal.textContent = fmtB(sizeHi.value);
filters.sizeLo = +sizeLo.value;
filters.sizeHi = +sizeHi.value;
paintTrack();
if (pushRender) render();
}
sizeLo.addEventListener('input', () => updateSizeUI(true));
sizeHi.addEventListener('input', () => updateSizeUI(true));
updateSizeUI(false);
const TOL = 1.00; // --- Filters ---
function matchesFilters(name) { function matchesFilters(name) {
const q = (search.value || '').toLowerCase(); const q = (search.value || '').toLowerCase();
if (q && !name.toLowerCase().includes(q)) return false; if (q && !name.toLowerCase().includes(q)) return false;
const info = byModel[name]; const info = byModel[name];
if (quantSel.value && (info.quant || '').toUpperCase() !== quantSel.value.toUpperCase()) return false; if (quantSel.value && (info.quant || '').toUpperCase() !== quantSel.value.toUpperCase()) return false;
if (info.sizeB != null && info.sizeB < parseFloat(size.value) - 1e-9) return false; if (info.sizeB != null && (info.sizeB < filters.sizeLo || info.sizeB > filters.sizeHi)) return false;
return true; return true;
} }
// remove quantization tokens from the title (we still show quant below)
function cleanTitle(name, quant) { function cleanTitle(name, quant) {
const qTokens = ['BF16', 'F16', 'F32', 'FP16', 'MXFP2', 'MXFP4', 'MXFP8']; const qTokens = ['BF16', 'F16', 'F32', 'FP16', 'MXFP2', 'MXFP4', 'MXFP8'];
if (quant) qTokens.push(quant.toUpperCase()); if (quant) qTokens.push(quant.toUpperCase());
let t = name; let t = name;
// common GGUF quant forms like Q4_K_M, Q8_0, Q6_K, Q3_K_S, etc.
t = t.replace(/[-_](Q\d+(?:_[A-Z0-9]+)*)/gi, ''); t = t.replace(/[-_](Q\d+(?:_[A-Z0-9]+)*)/gi, '');
// BF16/F16/F32/MXFPx anywhere
t = t.replace(new RegExp(`[-_]?(${qTokens.join('|')})\\b`, 'gi'), ''); t = t.replace(new RegExp(`[-_]?(${qTokens.join('|')})\\b`, 'gi'), '');
// drop "-UD"
t = t.replace(/-UD\b/gi, ''); t = t.replace(/-UD\b/gi, '');
// collapse leftover double separators
t = t.replace(/[-_]{2,}/g, '-') t = t.replace(/[-_]{2,}/g, '-')
.replace(/\s{2,}/g, ' ') .replace(/\s{2,}/g, ' ')
.replace(/[-_]+$/, '') .replace(/[-_]+$/, '')
@@ -487,7 +591,6 @@
} }
function winnersHTML(name, test, colDefs) { function winnersHTML(name, test, colDefs) {
// Collect (env, fa, mean, std) for visible columns
const vals = []; const vals = [];
for (const c of colDefs) { for (const c of colDefs) {
const v = byModel[name][test][c.key]; const v = byModel[name][test][c.key];
@@ -501,26 +604,19 @@
} }
} }
if (!vals.length) return '<span class="muted">—</span>'; if (!vals.length) return '<span class="muted">—</span>';
// Best backend by mean
let best = vals[0]; let best = vals[0];
for (const v of vals) if (v.mean > best.mean) best = v; for (const v of vals) if (v.mean > best.mean) best = v;
// Tie rule: within max(MIN_TOL, K*sqrt(std_best^2 + std_i^2))
const winners = []; const winners = [];
for (const v of vals) { for (const v of vals) {
const pooled = Math.sqrt((best.std || 0) ** 2 + (v.std || 0) ** 2); const pooled = Math.sqrt((best.std || 0) ** 2 + (v.std || 0) ** 2);
const tol = Math.max(MIN_TOL, K_SIGMA * pooled); const tol = Math.max(MIN_TOL, K_SIGMA * pooled);
if ((best.mean - v.mean) <= tol) winners.push(v); if ((best.mean - v.mean) <= tol) winners.push(v);
} }
// Render pills. If FA mode = both, show FA state on the pill.
if (faMode.value !== 'both') { if (faMode.value !== 'both') {
const envs = [...new Set(winners.map(w => w.env))]; const envs = [...new Set(winners.map(w => w.env))];
return `<div class="winner-wrap">${envs.map(env => return `<div class="winner-wrap">${envs.map(env =>
`<span class="winner-pill">🏆 ${env}</span>`).join('')}</div>`; `<span class="winner-pill">🏆 ${env}</span>`).join('')}</div>`;
} else { } else {
// group by env and list FA(s) that tied
const byEnv = {}; const byEnv = {};
for (const w of winners) (byEnv[w.env] ||= new Set()).add(w.fa); for (const w of winners) (byEnv[w.env] ||= new Set()).add(w.fa);
return `<div class="winner-wrap">${Object.entries(byEnv).map(([env, fas]) => { return `<div class="winner-wrap">${Object.entries(byEnv).map(([env, fas]) => {
@@ -534,27 +630,21 @@
function renderTable(prefix, test) { function renderTable(prefix, test) {
const envSel = selectedEnvs(); const mode = faMode.value; const envSel = selectedEnvs(); const mode = faMode.value;
const colDefs = buildColDefs(envSel, mode); const colDefs = buildColDefs(envSel, mode);
const h = document.getElementById(`${prefix}-h`); const h = document.getElementById(`${prefix}-h`);
h.innerHTML = headerHTML(test, colDefs); h.innerHTML = headerHTML(test, colDefs);
const tbody = document.querySelector(`#tbl-${prefix} tbody`); const tbody = document.querySelector(`#tbl-${prefix} tbody`);
tbody.innerHTML = ''; tbody.innerHTML = '';
for (const name of models.filter(matchesFilters)) { for (const name of models.filter(matchesFilters)) {
const tr = document.createElement('tr'); const tr = document.createElement('tr');
const tdM = document.createElement('td'); const tdM = document.createElement('td');
tdM.className = 'col-model'; tdM.className = 'col-model';
const info = byModel[name]; const info = byModel[name];
tdM.innerHTML = `<div class="model-title">${cleanTitle(name, info.quant)}</div><div class="model-meta">${info.quant || '—'} · ${(info.sizeB ?? '—')}B</div>`; tdM.innerHTML = `<div class="model-title">${cleanTitle(name, info.quant)}</div><div class="model-meta">${info.quant || '—'} · ${(info.sizeB ?? '—')}B</div>`;
tr.appendChild(tdM); tr.appendChild(tdM);
const tdW = document.createElement('td'); const tdW = document.createElement('td');
tdW.className = 'col-winner'; tdW.className = 'col-winner';
tdW.innerHTML = winnersHTML(name, test, colDefs); tdW.innerHTML = winnersHTML(name, test, colDefs);
tr.appendChild(tdW); tr.appendChild(tdW);
for (const c of colDefs) { for (const c of colDefs) {
const td = document.createElement('td'); const td = document.createElement('td');
td.innerHTML = cellHTML(name, test, c.key); td.innerHTML = cellHTML(name, test, c.key);
@@ -569,15 +659,12 @@
renderTable('tg', 'tg128'); renderTable('tg', 'tg128');
} }
// events
[search, quantSel, faMode].forEach(el => el.addEventListener('input', render)); [search, quantSel, faMode].forEach(el => el.addEventListener('input', render));
colPicker.addEventListener('change', render); colPicker.addEventListener('change', render);
size.addEventListener('input', e => { sizeOut.value = Number(e.target.value).toFixed(2); render(); });
sizeOut.addEventListener('input', e => { size.value = e.target.value; render(); });
render(); render();
})(); })();
</script> </script>
</body> </body>
</html> </html>