Spaces:
Running
Running
| // ββ State ββ | |
| let currentData = null; | |
| let currentGroup = 'All'; | |
| let currentDataset = 'combined'; | |
| let sortKey = 'cpt'; | |
| let sortDir = 'desc'; | |
| let searchQuery = ''; | |
| const $ = (sel) => document.querySelector(sel); | |
| const $$ = (sel) => document.querySelectorAll(sel); | |
| function formatNumber(n) { | |
| if (n === null || n === undefined) return '--'; | |
| return n.toLocaleString(); | |
| } | |
| function formatCompact(n) { | |
| if (n === null || n === undefined) return '--'; | |
| if (n >= 1e6) return (n / 1e6).toFixed(1) + 'M'; | |
| if (n >= 1e3) return (n / 1e3).toFixed(0) + 'K'; | |
| return n.toString(); | |
| } | |
| function getBackendClass(backend) { | |
| const map = { 'hf': 'backend-hf', 'tiktoken': 'backend-tiktoken', 'google': 'backend-google', 'anthropic': 'backend-anthropic', 'xai': 'backend-xai' }; | |
| return map[backend] || 'backend-hf'; | |
| } | |
| function getBackendLabel(backend) { | |
| const map = { 'hf': 'HF', 'tiktoken': 'OpenAI', 'google': 'Google', 'anthropic': 'Anthropic', 'xai': 'xAI' }; | |
| return map[backend] || backend; | |
| } | |
| // ββ Data Loading ββ | |
| function flattenData(data) { | |
| const rows = []; | |
| const allDatasets = new Set(); | |
| const datasetGroups = data.dataset_groups || {}; | |
| for (const [modelId, model] of Object.entries(data.models || {})) { | |
| for (const [dsName, ds] of Object.entries(model.datasets || {})) { | |
| allDatasets.add(dsName); | |
| rows.push({ | |
| model_id: modelId, | |
| model_name: model.model_name || modelId.split('/').pop(), | |
| release_date: model.release_date || '--', | |
| vocab_size: model.vocab_size, | |
| backend: inferBackend(modelId), | |
| dataset: dsName, | |
| group: datasetGroups[dsName] || 'Other', | |
| total_chars: ds.total_chars || 0, | |
| total_tokens: ds.total_tokens || 0, | |
| chars_per_token: ds.chars_per_token || 0, | |
| per_million_output_token_price: model.per_million_output_token_price, | |
| throughput: model.throughput, | |
| }); | |
| } | |
| } | |
| const groups = [...new Set(Object.values(datasetGroups))].sort(); | |
| return { | |
| rows, | |
| datasets: Array.from(allDatasets).sort(), | |
| groups, | |
| datasetGroups, | |
| timestamp: data.timestamp || '' | |
| }; | |
| } | |
| function inferBackend(modelId) { | |
| if (modelId.startsWith('gpt-') || modelId.startsWith('text-embedding')) return 'tiktoken'; | |
| if (modelId.startsWith('gemini-')) return 'google'; | |
| if (modelId.startsWith('claude-')) return 'anthropic'; | |
| if (modelId.startsWith('grok-')) return 'xai'; | |
| return 'hf'; | |
| } | |
| async function loadData() { | |
| try { | |
| const res = await fetch('data.json'); | |
| if (!res.ok) throw new Error('data.json not found'); | |
| const data = await res.json(); | |
| if (!data.models) throw new Error('invalid format'); | |
| currentData = flattenData(data); | |
| renderGroupTabs(); | |
| renderDatasetTabs(); | |
| renderStats(); | |
| renderTable(); | |
| renderChart(); | |
| $('#timestamp').textContent = currentData.timestamp ? 'run: ' + currentData.timestamp : ''; | |
| } catch (e) { | |
| $('#tableBody').innerHTML = `<tr><td colspan="11" class="empty-state"><h3>oops!</h3><p>could not load data.json β ${e.message}</p></td></tr>`; | |
| $('#chartContainer').innerHTML = '<div class="chart-placeholder">no data.json found</div>'; | |
| } | |
| } | |
| // ββ Group Tabs ββ | |
| function renderGroupTabs() { | |
| const tabs = $('#groupTabs'); | |
| tabs.innerHTML = ''; | |
| const groups = ['All', ...currentData.groups]; | |
| for (const g of groups) { | |
| const btn = document.createElement('button'); | |
| btn.className = 'tab-btn group-tab' + (currentGroup === g ? ' active' : ''); | |
| btn.textContent = g; | |
| btn.onclick = () => { | |
| currentGroup = g; | |
| currentDataset = 'combined'; | |
| if (g === 'All') { | |
| $('#datasetBar').style.display = 'none'; | |
| } else { | |
| $('#datasetBar').style.display = 'flex'; | |
| } | |
| renderGroupTabs(); | |
| renderDatasetTabs(); | |
| renderStats(); | |
| renderTable(); | |
| renderChart(); | |
| }; | |
| tabs.appendChild(btn); | |
| } | |
| } | |
| // ββ Dataset Tabs ββ | |
| function renderDatasetTabs() { | |
| const tabs = $('#datasetTabs'); | |
| tabs.innerHTML = ''; | |
| if (currentGroup === 'All') return; | |
| const combinedBtn = document.createElement('button'); | |
| combinedBtn.className = 'tab-btn' + (currentDataset === 'combined' ? ' active' : ''); | |
| combinedBtn.textContent = 'combined'; | |
| combinedBtn.onclick = () => { currentDataset = 'combined'; renderDatasetTabs(); renderStats(); renderTable(); renderChart(); }; | |
| tabs.appendChild(combinedBtn); | |
| const dsInGroup = currentData.datasets.filter(ds => currentData.datasetGroups[ds] === currentGroup); | |
| for (const ds of dsInGroup) { | |
| const btn = document.createElement('button'); | |
| btn.className = 'tab-btn' + (currentDataset === ds ? ' active' : ''); | |
| btn.textContent = ds; | |
| btn.onclick = () => { currentDataset = ds; renderDatasetTabs(); renderStats(); renderTable(); renderChart(); }; | |
| tabs.appendChild(btn); | |
| } | |
| } | |
| // ββ Stats ββ | |
| function renderStats() { | |
| const rows = getFilteredRows(); | |
| const models = new Set(rows.map(r => r.model_id)); | |
| let bestName = 'β'; | |
| let totalChars = 0; | |
| if (rows.length) { | |
| const sorted = [...rows].sort((a, b) => b.chars_per_token - a.chars_per_token); | |
| bestName = sorted[0].model_name; | |
| totalChars = sorted[0].total_chars; | |
| } | |
| $('#statModels').textContent = models.size; | |
| $('#statChars').textContent = formatCompact(totalChars); | |
| $('#statBest').textContent = bestName.length > 14 ? bestName.slice(0, 12) + '..' : bestName; | |
| } | |
| // ββ Filter & Sort ββ | |
| function getFilteredRows() { | |
| let rows; | |
| if (currentGroup === 'All' || currentDataset === 'combined') { | |
| const targetDatasets = currentGroup === 'All' | |
| ? currentData.datasets | |
| : currentData.datasets.filter(ds => currentData.datasetGroups[ds] === currentGroup); | |
| const modelIds = [...new Set(currentData.rows.map(r => r.model_id))]; | |
| rows = []; | |
| for (const mid of modelIds) { | |
| const mrows = currentData.rows.filter(r => r.model_id === mid && targetDatasets.includes(r.dataset)); | |
| if (!mrows.length) continue; | |
| const total_chars = mrows.reduce((s, r) => s + r.total_chars, 0); | |
| const total_tokens = mrows.reduce((s, r) => s + r.total_tokens, 0); | |
| const cpts = mrows.map(r => r.total_tokens > 0 ? r.total_chars / r.total_tokens : 0); | |
| const macroCpt = cpts.length > 0 ? cpts.reduce((a, b) => a + b, 0) / cpts.length : 0; | |
| rows.push({ | |
| model_id: mid, | |
| model_name: mrows[0].model_name, | |
| release_date: mrows[0].release_date, | |
| vocab_size: mrows[0].vocab_size, | |
| backend: mrows[0].backend, | |
| total_chars, | |
| total_tokens, | |
| chars_per_token: macroCpt, | |
| per_million_output_token_price: mrows[0].per_million_output_token_price, | |
| throughput: mrows[0].throughput, | |
| }); | |
| } | |
| } else { | |
| rows = currentData.rows.filter(r => r.dataset === currentDataset).map(r => ({ ...r })); | |
| } | |
| if (searchQuery) { | |
| const q = searchQuery.toLowerCase(); | |
| rows = rows.filter(r => r.model_name.toLowerCase().includes(q) || r.model_id.toLowerCase().includes(q)); | |
| } | |
| // Compute adjusted values: adjusted = raw * C/T for the current view | |
| for (const r of rows) { | |
| const cpt = r.chars_per_token || 0; | |
| if (r.per_million_output_token_price != null && r.per_million_output_token_price !== undefined) { | |
| r.price_adj = cpt ? r.per_million_output_token_price / cpt : null; | |
| } else { | |
| r.price_adj = null; | |
| } | |
| if (r.throughput != null && r.throughput !== undefined) { | |
| r.throughput_adj = r.throughput * cpt; | |
| } else { | |
| r.throughput_adj = null; | |
| } | |
| } | |
| if (rows.length) { | |
| const maxCPT = Math.max(...rows.map(r => r.chars_per_token)); | |
| for (const r of rows) { | |
| // "% more tokens than the best tokenizer for identical text" β matches | |
| // analyze_benchmark.py's compute_pct_more_tokens(): (best/mine - 1) * 100. | |
| // NOT (best-mine)/best β that's a different, smaller number that understates | |
| // the gap for anything far from the best (e.g. reads ~41% where this reads ~69%). | |
| r.pct_more = r.chars_per_token > 0 ? ((maxCPT / r.chars_per_token) - 1) * 100 : 0; | |
| } | |
| } | |
| return rows; | |
| } | |
| function sortRows(rows) { | |
| const keyMap = { | |
| 'rank': 'chars_per_token', 'model': 'model_name', 'backend': 'backend', | |
| 'date': 'release_date', 'vocab': 'vocab_size', 'tokens': 'total_tokens', | |
| 'cpt': 'chars_per_token', | |
| 'price': 'per_million_output_token_price', 'price_adj': 'price_adj', | |
| 'throughput': 'throughput', 'throughput_adj': 'throughput_adj', 'tax': 'pct_more' | |
| }; | |
| const key = keyMap[sortKey] || sortKey; | |
| const dir = sortDir === 'asc' ? 1 : -1; | |
| return [...rows].sort((a, b) => { | |
| let va = a[key]; | |
| let vb = b[key]; | |
| if (typeof va === 'string') va = va.toLowerCase(); | |
| if (typeof vb === 'string') vb = vb.toLowerCase(); | |
| if (va === null || va === undefined) va = -Infinity; | |
| if (vb === null || vb === undefined) vb = -Infinity; | |
| if (va < vb) return -1 * dir; | |
| if (va > vb) return 1 * dir; | |
| return 0; | |
| }); | |
| } | |
| // ββ Table ββ | |
| function renderTable() { | |
| const tbody = $('#tableBody'); | |
| let rows = getFilteredRows(); | |
| if (rows.length === 0) { | |
| tbody.innerHTML = `<tr><td colspan="11" class="empty-state"><h3>no results</h3><p>try a different search or dataset</p></td></tr>`; | |
| return; | |
| } | |
| rows = sortRows(rows); | |
| const ranked = rows.map((r, i) => ({ ...r, rank: i + 1 })); | |
| const html = ranked.map(r => { | |
| const rankClass = r.rank === 1 ? 'rank-1' : r.rank === 2 ? 'rank-2' : r.rank === 3 ? 'rank-3' : 'rank-other'; | |
| const vocab = (r.vocab_size !== null && r.vocab_size !== undefined) ? formatCompact(r.vocab_size) : '--'; | |
| const backendClass = getBackendClass(r.backend); | |
| const backendLabel = getBackendLabel(r.backend); | |
| // Price: '--' if null/undefined | |
| const priceVal = (r.per_million_output_token_price != null && r.per_million_output_token_price !== undefined) | |
| ? r.per_million_output_token_price.toFixed(2) | |
| : '--'; | |
| // Price Adjusted: '--' if null/undefined | |
| const priceAdjVal = (r.price_adj != null && r.price_adj !== undefined) | |
| ? r.price_adj.toFixed(2) | |
| : '--'; | |
| // Throughput: '--' if null/undefined | |
| const tputVal = (r.throughput != null && r.throughput !== undefined) | |
| ? formatCompact(r.throughput) | |
| : '--'; | |
| // Throughput Adjusted: Rounded to 0 decimals, '--' if null/undefined | |
| const tputAdjVal = (r.throughput_adj != null && r.throughput_adj !== undefined) | |
| ? Math.round(r.throughput_adj).toLocaleString() | |
| : '--'; | |
| // Tokenizer Tax: extra tokens vs the most efficient tokenizer in this view. | |
| // 0 means this row IS the most efficient tokenizer β shown as a dash, not "+0.0%". | |
| const taxVal = (r.pct_more != null && r.pct_more > 0) | |
| ? '+' + r.pct_more.toFixed(1) + '%' | |
| : 'β'; | |
| return ` | |
| <tr data-model="${r.model_id}"> | |
| <td class="col-rank"><span class="rank-badge ${rankClass}">${r.rank}</span></td> | |
| <td class="col-model"> | |
| <div class="model-name">${r.model_name}</div> | |
| <div class="model-id">${r.model_id}</div> | |
| </td> | |
| <td class="col-backend"><span class="backend-badge ${backendClass}">${backendLabel}</span></td> | |
| <td class="col-vocab">${vocab}</td> | |
| <td class="col-tokens">${formatCompact(r.total_tokens)}</td> | |
| <td class="col-cpt">${r.chars_per_token.toFixed(2)}</td> | |
| <td class="col-price">${priceVal}</td> | |
| <td class="col-price-adj">${priceAdjVal}</td> | |
| <td class="col-throughput">${tputVal}</td> | |
| <td class="col-throughput-adj">${tputAdjVal}</td> | |
| <td class="col-tax">${taxVal}</td> | |
| </tr> | |
| `; | |
| }).join(''); | |
| tbody.innerHTML = html; | |
| } | |
| // ββ Hand-drawn Chart ββ | |
| function renderChart() { | |
| const container = $('#chartContainer'); | |
| let rows = getFilteredRows(); | |
| if (rows.length === 0) { | |
| container.innerHTML = '<div class="chart-placeholder">no data to sketch</div>'; | |
| return; | |
| } | |
| rows = sortRows(rows); | |
| const displayRows = rows; | |
| const maxCPT = Math.max(...displayRows.map(r => r.chars_per_token)) || 1; | |
| const barHeight = 34; | |
| const barGap = 14; | |
| const labelW = 170; | |
| const chartW = 720; | |
| const chartH = displayRows.length * (barHeight + barGap) + 70; | |
| const barMaxW = chartW - labelW - 90; | |
| const colors = ['#2a9d8f', '#58a6ff', '#e76f51', '#e9c46a', '#8ab17d', '#f4a261', '#2a9d8f', '#58a6ff', '#e76f51', '#e9c46a']; | |
| let svg = `<svg class="chart-svg" viewBox="0 0 ${chartW} ${chartH}" xmlns="http://www.w3.org/2000/svg">`; | |
| const plotBottom = chartH - 42; | |
| for (let i = 0; i <= 4; i++) { | |
| const x = labelW + (barMaxW * i / 4); | |
| const val = (maxCPT * i / 4).toFixed(1); | |
| const wobble = (Math.random() - 0.5) * 1.5; | |
| svg += `<path d="M${x + wobble} 18 Q${x} ${chartH / 2} ${x - wobble} ${plotBottom}" stroke="var(--border-sketch)" stroke-width="1" fill="none" stroke-dasharray="4,3" opacity="0.6"/>`; | |
| svg += `<text x="${x}" y="${chartH - 16}" text-anchor="middle" fill="var(--ink-muted)" font-family="Kalam" font-size="12" font-weight="700">${val}</text>`; | |
| } | |
| svg += `<path d="M${labelW - 5} ${plotBottom} Q${chartW / 2} ${plotBottom + 2} ${chartW - 40} ${plotBottom}" stroke="var(--ink-light)" stroke-width="1.5" fill="none"/>`; | |
| displayRows.forEach((r, i) => { | |
| const y = 18 + i * (barHeight + barGap); | |
| const width = (r.chars_per_token / maxCPT) * barMaxW; | |
| const color = colors[i % colors.length]; | |
| const jitter = (Math.random() - 0.5) * 2; | |
| const x1 = labelW + jitter; | |
| const y1 = y + jitter; | |
| const x2 = labelW + width + jitter; | |
| const y2 = y + barHeight + jitter; | |
| const curve = 3 + Math.random() * 2; | |
| const patternId = `hatch${i}`; | |
| svg += `<defs> | |
| <pattern id="${patternId}" patternUnits="userSpaceOnUse" width="6" height="6" patternTransform="rotate(45)"> | |
| <line x1="0" y1="0" x2="0" y2="6" stroke="${color}" stroke-width="1" opacity="0.15"/> | |
| </pattern> | |
| </defs>`; | |
| const wob = () => (Math.random() - 0.5) * 1.5; | |
| svg += `<path d="M${x1} ${y1 + curve} Q${x1} ${y1} ${x1 + curve} ${y1} L${x2 - curve} ${y1 + wob()} Q${x2} ${y1} ${x2} ${y1 + curve} L${x2 + wob()} ${y2 - curve} Q${x2} ${y2} ${x2 - curve} ${y2} L${x1 + curve} ${y2 - wob()} Q${x1} ${y2} ${x1} ${y2 - curve} Z" | |
| fill="${color}" opacity="0.88" stroke="${color}" stroke-width="1.5" stroke-linejoin="round"/>`; | |
| svg += `<path d="M${x1} ${y1 + curve} L${x2 - curve} ${y1} Q${x2 + 1} ${(y1 + y2) / 2} ${x2 - curve} ${y2} L${x1} ${y2 - curve} Z" | |
| fill="url(#${patternId})" opacity="0.4"/>`; | |
| // Model name label (left) β sketchy hand font | |
| svg += `<text x="${labelW - 10}" y="${y + barHeight / 2 + 5}" text-anchor="end" fill="var(--ink)" font-family="Kalam" font-size="13" font-weight="700">${r.model_name}</text>`; | |
| // Value read-out (right of bar) β white + bold/blunt, handwritten Kalam font | |
| svg += `<text x="${labelW + width + 8}" y="${y + barHeight / 2 + 5}" fill="#f0f6fc" font-family="Kalam" font-size="13" font-weight="700">${r.chars_per_token.toFixed(2)}</text>`; | |
| }); | |
| svg += '</svg>'; | |
| container.innerHTML = svg; | |
| const viewLabel = currentGroup === 'All' ? 'All Datasets' : | |
| currentDataset === 'combined' ? currentGroup : currentDataset; | |
| $('#chartTitle').textContent = `Efficiency Sketch β ${viewLabel}`; | |
| } | |
| // ββ Event Listeners ββ | |
| $('#searchInput').addEventListener('input', (e) => { | |
| searchQuery = e.target.value; | |
| renderTable(); | |
| renderChart(); | |
| }); | |
| $$('.leaderboard th[data-sort]').forEach(th => { | |
| th.addEventListener('click', () => { | |
| const key = th.dataset.sort; | |
| if (sortKey === key) { | |
| sortDir = sortDir === 'asc' ? 'desc' : 'asc'; | |
| } else { | |
| sortKey = key; | |
| sortDir = key === 'model' || key === 'date' ? 'asc' : 'desc'; | |
| } | |
| $$('.leaderboard th').forEach(t => t.classList.remove('asc', 'desc')); | |
| th.classList.add(sortDir); | |
| renderTable(); | |
| renderChart(); | |
| }); | |
| }); | |
| // ββ Init ββ | |
| loadData(); |