Spaces:
Running
Running
GitHub Actions commited on
Commit ·
ed5d4b6
1
Parent(s): 86d8a2e
sync from abhijitramesh/webgpu-bench@ef7e64472d
Browse files- build/asyncify/build-info.json +1 -1
- build/jspi/build-info.json +1 -1
- js/app.js +1 -1
- js/charts.js +9 -36
- js/dataset.js +3 -10
- js/tables.js +4 -9
- js/utils.js +5 -0
build/asyncify/build-info.json
CHANGED
|
@@ -2,5 +2,5 @@
|
|
| 2 |
"llamaCppCommit": "f22c8021d213567942a3d0134692e70f02f28f3a",
|
| 3 |
"llamaCppDescribe": "b8981-3-gf22c8021d",
|
| 4 |
"dawnTag": "v20260317.182325",
|
| 5 |
-
"builtAt": "2026-05-11T23:
|
| 6 |
}
|
|
|
|
| 2 |
"llamaCppCommit": "f22c8021d213567942a3d0134692e70f02f28f3a",
|
| 3 |
"llamaCppDescribe": "b8981-3-gf22c8021d",
|
| 4 |
"dawnTag": "v20260317.182325",
|
| 5 |
+
"builtAt": "2026-05-11T23:13:42Z"
|
| 6 |
}
|
build/jspi/build-info.json
CHANGED
|
@@ -2,5 +2,5 @@
|
|
| 2 |
"llamaCppCommit": "f22c8021d213567942a3d0134692e70f02f28f3a",
|
| 3 |
"llamaCppDescribe": "b8981-3-gf22c8021d",
|
| 4 |
"dawnTag": "v20260317.182325",
|
| 5 |
-
"builtAt": "2026-05-11T23:
|
| 6 |
}
|
|
|
|
| 2 |
"llamaCppCommit": "f22c8021d213567942a3d0134692e70f02f28f3a",
|
| 3 |
"llamaCppDescribe": "b8981-3-gf22c8021d",
|
| 4 |
"dawnTag": "v20260317.182325",
|
| 5 |
+
"builtAt": "2026-05-11T23:10:21Z"
|
| 6 |
}
|
js/app.js
CHANGED
|
@@ -224,7 +224,7 @@ function renderHeroMeta(data) {
|
|
| 224 |
/* Tween numeric content from 0 to a target. CSS-only via @property would
|
| 225 |
need server-side @property registration to work in older Safari; keep
|
| 226 |
this 12-line JS tween for predictability. */
|
| 227 |
-
|
| 228 |
if (!el) return;
|
| 229 |
const start = parseFloat(el.dataset.value || '0') || 0;
|
| 230 |
const end = Number(target) || 0;
|
|
|
|
| 224 |
/* Tween numeric content from 0 to a target. CSS-only via @property would
|
| 225 |
need server-side @property registration to work in older Safari; keep
|
| 226 |
this 12-line JS tween for predictability. */
|
| 227 |
+
function animateCount(el, target, { decimals = 0, duration = 600 } = {}) {
|
| 228 |
if (!el) return;
|
| 229 |
const start = parseFloat(el.dataset.value || '0') || 0;
|
| 230 |
const end = Number(target) || 0;
|
js/charts.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import { BROWSER_COLORS, quantSortKey, groupBy, formatTokS } from './utils.js';
|
| 2 |
import { expandCpuRows } from './data.js';
|
| 3 |
|
| 4 |
// Global Chart.js theme — uses the site's font tokens and a calm tooltip
|
|
@@ -78,14 +78,10 @@ export function renderDecodeChart(results) {
|
|
| 78 |
|
| 79 |
const passed = results.filter(r => r.status === 'done' && r.decode_tok_s != null);
|
| 80 |
if (passed.length === 0) {
|
| 81 |
-
|
| 82 |
-
const msg = document.createElement('div');
|
| 83 |
-
msg.className = 'chart-empty';
|
| 84 |
-
msg.textContent = 'No data';
|
| 85 |
-
canvas.parentElement.appendChild(msg);
|
| 86 |
return;
|
| 87 |
}
|
| 88 |
-
|
| 89 |
|
| 90 |
const byBrowser = groupBy(passed, 'browser');
|
| 91 |
const allQuants = [...new Set(passed.map(r => r.variant))].sort((a, b) => quantSortKey(a) - quantSortKey(b));
|
|
@@ -95,12 +91,7 @@ export function renderDecodeChart(results) {
|
|
| 95 |
return {
|
| 96 |
label: browser,
|
| 97 |
backgroundColor: BROWSER_COLORS[browser] || '#888',
|
| 98 |
-
data: allQuants.map(q =>
|
| 99 |
-
const group = byQuant[q];
|
| 100 |
-
if (!group) return null;
|
| 101 |
-
const vals = group.map(r => r.decode_tok_s).filter(v => v != null);
|
| 102 |
-
return vals.length ? vals.reduce((a, b) => a + b, 0) / vals.length : null;
|
| 103 |
-
}),
|
| 104 |
};
|
| 105 |
});
|
| 106 |
|
|
@@ -130,14 +121,10 @@ export function renderPrefillChart(results) {
|
|
| 130 |
|
| 131 |
const passed = results.filter(r => r.status === 'done' && r.prefill_tok_s != null);
|
| 132 |
if (passed.length === 0) {
|
| 133 |
-
|
| 134 |
-
const msg = document.createElement('div');
|
| 135 |
-
msg.className = 'chart-empty';
|
| 136 |
-
msg.textContent = 'No data';
|
| 137 |
-
canvas.parentElement.appendChild(msg);
|
| 138 |
return;
|
| 139 |
}
|
| 140 |
-
|
| 141 |
|
| 142 |
const byBrowser = groupBy(passed, 'browser');
|
| 143 |
const allQuants = [...new Set(passed.map(r => r.variant))].sort((a, b) => quantSortKey(a) - quantSortKey(b));
|
|
@@ -147,12 +134,7 @@ export function renderPrefillChart(results) {
|
|
| 147 |
return {
|
| 148 |
label: browser,
|
| 149 |
backgroundColor: BROWSER_COLORS[browser] || '#888',
|
| 150 |
-
data: allQuants.map(q =>
|
| 151 |
-
const group = byQuant[q];
|
| 152 |
-
if (!group) return null;
|
| 153 |
-
const vals = group.map(r => r.prefill_tok_s).filter(v => v != null);
|
| 154 |
-
return vals.length ? vals.reduce((a, b) => a + b, 0) / vals.length : null;
|
| 155 |
-
}),
|
| 156 |
};
|
| 157 |
});
|
| 158 |
|
|
@@ -182,14 +164,10 @@ export function renderSizeChart(results) {
|
|
| 182 |
|
| 183 |
const passed = results.filter(r => r.status === 'done' && r.decode_tok_s != null && r.sizeMB);
|
| 184 |
if (passed.length === 0) {
|
| 185 |
-
|
| 186 |
-
const msg = document.createElement('div');
|
| 187 |
-
msg.className = 'chart-empty';
|
| 188 |
-
msg.textContent = 'No data';
|
| 189 |
-
canvas.parentElement.appendChild(msg);
|
| 190 |
return;
|
| 191 |
}
|
| 192 |
-
|
| 193 |
|
| 194 |
const byBrowser = groupBy(passed, 'browser');
|
| 195 |
|
|
@@ -245,11 +223,6 @@ const METRIC_LABELS = {
|
|
| 245 |
prefill_tok_s: 'Prefill tok/s',
|
| 246 |
};
|
| 247 |
|
| 248 |
-
function avgBy(items, field) {
|
| 249 |
-
const vals = items.map(r => r[field]).filter(v => v != null);
|
| 250 |
-
return vals.length ? vals.reduce((a, b) => a + b, 0) / vals.length : null;
|
| 251 |
-
}
|
| 252 |
-
|
| 253 |
// CPU is pinned to d=0 by the runner, so apples-to-apples means reading
|
| 254 |
// GPU's d=0 number. The CPU side keeps its bare metric (CPU records are
|
| 255 |
// depth-pinned to 0 either way); GPU reads `<metric>_d0`. Plain-Run
|
|
|
|
| 1 |
+
import { BROWSER_COLORS, quantSortKey, groupBy, formatTokS, avgBy } from './utils.js';
|
| 2 |
import { expandCpuRows } from './data.js';
|
| 3 |
|
| 4 |
// Global Chart.js theme — uses the site's font tokens and a calm tooltip
|
|
|
|
| 78 |
|
| 79 |
const passed = results.filter(r => r.status === 'done' && r.decode_tok_s != null);
|
| 80 |
if (passed.length === 0) {
|
| 81 |
+
showEmptyState(canvas);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
return;
|
| 83 |
}
|
| 84 |
+
clearEmptyState(canvas);
|
| 85 |
|
| 86 |
const byBrowser = groupBy(passed, 'browser');
|
| 87 |
const allQuants = [...new Set(passed.map(r => r.variant))].sort((a, b) => quantSortKey(a) - quantSortKey(b));
|
|
|
|
| 91 |
return {
|
| 92 |
label: browser,
|
| 93 |
backgroundColor: BROWSER_COLORS[browser] || '#888',
|
| 94 |
+
data: allQuants.map(q => avgBy(byQuant[q] || [], 'decode_tok_s')),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
};
|
| 96 |
});
|
| 97 |
|
|
|
|
| 121 |
|
| 122 |
const passed = results.filter(r => r.status === 'done' && r.prefill_tok_s != null);
|
| 123 |
if (passed.length === 0) {
|
| 124 |
+
showEmptyState(canvas);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
return;
|
| 126 |
}
|
| 127 |
+
clearEmptyState(canvas);
|
| 128 |
|
| 129 |
const byBrowser = groupBy(passed, 'browser');
|
| 130 |
const allQuants = [...new Set(passed.map(r => r.variant))].sort((a, b) => quantSortKey(a) - quantSortKey(b));
|
|
|
|
| 134 |
return {
|
| 135 |
label: browser,
|
| 136 |
backgroundColor: BROWSER_COLORS[browser] || '#888',
|
| 137 |
+
data: allQuants.map(q => avgBy(byQuant[q] || [], 'prefill_tok_s')),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
};
|
| 139 |
});
|
| 140 |
|
|
|
|
| 164 |
|
| 165 |
const passed = results.filter(r => r.status === 'done' && r.decode_tok_s != null && r.sizeMB);
|
| 166 |
if (passed.length === 0) {
|
| 167 |
+
showEmptyState(canvas);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
return;
|
| 169 |
}
|
| 170 |
+
clearEmptyState(canvas);
|
| 171 |
|
| 172 |
const byBrowser = groupBy(passed, 'browser');
|
| 173 |
|
|
|
|
| 223 |
prefill_tok_s: 'Prefill tok/s',
|
| 224 |
};
|
| 225 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
// CPU is pinned to d=0 by the runner, so apples-to-apples means reading
|
| 227 |
// GPU's d=0 number. The CPU side keeps its bare metric (CPU records are
|
| 228 |
// depth-pinned to 0 either way); GPU reads `<metric>_d0`. Plain-Run
|
js/dataset.js
CHANGED
|
@@ -31,7 +31,7 @@ const MAX_FETCH = 1000;
|
|
| 31 |
look newer than `sinceISO` (with a clock-skew buffer applied). On any
|
| 32 |
network/CORS/parse failure, returns an empty array — the dashboard then
|
| 33 |
silently falls back to the static combined.json baseline. */
|
| 34 |
-
|
| 35 |
if (!datasetRepo) return [];
|
| 36 |
// Cache-bust the listing — HF's CDN can serve a stale tree response, and
|
| 37 |
// we specifically care about reading-our-own-write after a submit.
|
|
@@ -64,19 +64,12 @@ async function fetchRunFile(datasetRepo, filePath) {
|
|
| 64 |
return resp.json();
|
| 65 |
}
|
| 66 |
|
| 67 |
-
/* List the dataset tree and download every file in `runs/`.
|
| 68 |
-
|
| 69 |
-
Caller is responsible for rate-limiting/caching. */
|
| 70 |
export async function fetchAllRuns(datasetRepo) {
|
| 71 |
return fetchRunsBatch(datasetRepo, await listRecentRunFiles(datasetRepo, null));
|
| 72 |
}
|
| 73 |
|
| 74 |
-
/* List the dataset tree and download every file that's newer than the
|
| 75 |
-
baseline's generatedAt. Kept for callers that still want a delta view. */
|
| 76 |
-
export async function fetchRecentRuns(datasetRepo, sinceISO) {
|
| 77 |
-
return fetchRunsBatch(datasetRepo, await listRecentRunFiles(datasetRepo, sinceISO));
|
| 78 |
-
}
|
| 79 |
-
|
| 80 |
async function fetchRunsBatch(datasetRepo, files) {
|
| 81 |
if (files.length === 0) return { records: [], machines: [], fileCount: 0 };
|
| 82 |
|
|
|
|
| 31 |
look newer than `sinceISO` (with a clock-skew buffer applied). On any
|
| 32 |
network/CORS/parse failure, returns an empty array — the dashboard then
|
| 33 |
silently falls back to the static combined.json baseline. */
|
| 34 |
+
async function listRecentRunFiles(datasetRepo, sinceISO) {
|
| 35 |
if (!datasetRepo) return [];
|
| 36 |
// Cache-bust the listing — HF's CDN can serve a stale tree response, and
|
| 37 |
// we specifically care about reading-our-own-write after a submit.
|
|
|
|
| 64 |
return resp.json();
|
| 65 |
}
|
| 66 |
|
| 67 |
+
/* List the dataset tree and download every file in `runs/`. Caller is
|
| 68 |
+
responsible for rate-limiting/caching. */
|
|
|
|
| 69 |
export async function fetchAllRuns(datasetRepo) {
|
| 70 |
return fetchRunsBatch(datasetRepo, await listRecentRunFiles(datasetRepo, null));
|
| 71 |
}
|
| 72 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
async function fetchRunsBatch(datasetRepo, files) {
|
| 74 |
if (files.length === 0) return { records: [], machines: [], fileCount: 0 };
|
| 75 |
|
js/tables.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import { formatTokS, formatMs, categorizeError, groupBy, quantSortKey } from './utils.js';
|
| 2 |
import { expandCpuRows } from './data.js';
|
| 3 |
|
| 4 |
let lastResults = [];
|
|
@@ -373,11 +373,6 @@ export function renderCpuGpuTable(results) {
|
|
| 373 |
return;
|
| 374 |
}
|
| 375 |
|
| 376 |
-
function avg(items, field) {
|
| 377 |
-
const vals = items.map(r => r[field]).filter(v => v != null);
|
| 378 |
-
return vals.length ? vals.reduce((a, b) => a + b, 0) / vals.length : null;
|
| 379 |
-
}
|
| 380 |
-
|
| 381 |
const gpuBrowsers = [...new Set(gpuResults.map(r => r.browser))].sort();
|
| 382 |
|
| 383 |
const cpuByModelVariant = groupBy(cpuResults, r => `${r.model}::${r.variant}`);
|
|
@@ -437,7 +432,7 @@ export function renderCpuGpuTable(results) {
|
|
| 437 |
|
| 438 |
// CPU columns
|
| 439 |
for (const m of METRICS) {
|
| 440 |
-
const val =
|
| 441 |
html += `<td><span class="mono">${formatTokS(val)}</span></td>`;
|
| 442 |
}
|
| 443 |
|
|
@@ -445,8 +440,8 @@ export function renderCpuGpuTable(results) {
|
|
| 445 |
for (const b of gpuBrowsers) {
|
| 446 |
const gpuItems = gpuByBrowser[b] || [];
|
| 447 |
for (const m of METRICS) {
|
| 448 |
-
const cpuVal =
|
| 449 |
-
const gpuVal =
|
| 450 |
const speedup = cpuVal && gpuVal ? gpuVal / cpuVal : null;
|
| 451 |
const cls = speedup == null ? '' : speedup >= 3 ? 'text-success' : speedup >= 1.5 ? '' : speedup >= 1 ? 'text-muted' : 'text-error';
|
| 452 |
html += `<td><span class="mono">${formatTokS(gpuVal)}</span></td>`;
|
|
|
|
| 1 |
+
import { formatTokS, formatMs, categorizeError, groupBy, quantSortKey, avgBy } from './utils.js';
|
| 2 |
import { expandCpuRows } from './data.js';
|
| 3 |
|
| 4 |
let lastResults = [];
|
|
|
|
| 373 |
return;
|
| 374 |
}
|
| 375 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 376 |
const gpuBrowsers = [...new Set(gpuResults.map(r => r.browser))].sort();
|
| 377 |
|
| 378 |
const cpuByModelVariant = groupBy(cpuResults, r => `${r.model}::${r.variant}`);
|
|
|
|
| 432 |
|
| 433 |
// CPU columns
|
| 434 |
for (const m of METRICS) {
|
| 435 |
+
const val = avgBy(cpuItems, m.cpuField);
|
| 436 |
html += `<td><span class="mono">${formatTokS(val)}</span></td>`;
|
| 437 |
}
|
| 438 |
|
|
|
|
| 440 |
for (const b of gpuBrowsers) {
|
| 441 |
const gpuItems = gpuByBrowser[b] || [];
|
| 442 |
for (const m of METRICS) {
|
| 443 |
+
const cpuVal = avgBy(cpuItems, m.cpuField);
|
| 444 |
+
const gpuVal = avgBy(gpuItems, m.gpuField);
|
| 445 |
const speedup = cpuVal && gpuVal ? gpuVal / cpuVal : null;
|
| 446 |
const cls = speedup == null ? '' : speedup >= 3 ? 'text-success' : speedup >= 1.5 ? '' : speedup >= 1 ? 'text-muted' : 'text-error';
|
| 447 |
html += `<td><span class="mono">${formatTokS(gpuVal)}</span></td>`;
|
js/utils.js
CHANGED
|
@@ -52,3 +52,8 @@ export function groupBy(arr, keyFn) {
|
|
| 52 |
}
|
| 53 |
return map;
|
| 54 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
}
|
| 53 |
return map;
|
| 54 |
}
|
| 55 |
+
|
| 56 |
+
export function avgBy(items, field) {
|
| 57 |
+
const vals = items.map(r => r[field]).filter(v => v != null);
|
| 58 |
+
return vals.length ? vals.reduce((a, b) => a + b, 0) / vals.length : null;
|
| 59 |
+
}
|