GitHub Actions commited on
Commit
bc8e1d3
·
1 Parent(s): f221926

sync from abhijitramesh/webgpu-bench@f3ee9fc069

Browse files
build/asyncify/bench.js CHANGED
The diff for this file is too large to render. See raw diff
 
build/asyncify/bench.wasm CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:50895b262f9b0da117509d04075ca06f3b30d3482c130d22c827e53e20d8a650
3
- size 5233188
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1ba4381b1a8c3a34d003bae0837e684515174b8c1e24b470b7013eedafc359e4
3
+ size 5235854
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-04-29T23:41:53Z"
6
  }
 
2
  "llamaCppCommit": "f22c8021d213567942a3d0134692e70f02f28f3a",
3
  "llamaCppDescribe": "b8981-3-gf22c8021d",
4
  "dawnTag": "v20260317.182325",
5
+ "builtAt": "2026-05-01T08:36:58Z"
6
  }
build/jspi/bench.js CHANGED
The diff for this file is too large to render. See raw diff
 
build/jspi/bench.wasm CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:92ef71c59da832ad869cbc002665fd3bb3505c7e515a7cefc5d7f7901224ea40
3
- size 3612135
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:39eea46a9d02b044c9d143cf8243c3e05f8bf89d94bb5bcd804b6e43755b958d
3
+ size 3614207
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-04-29T23:37:54Z"
6
  }
 
2
  "llamaCppCommit": "f22c8021d213567942a3d0134692e70f02f28f3a",
3
  "llamaCppDescribe": "b8981-3-gf22c8021d",
4
  "dawnTag": "v20260317.182325",
5
+ "builtAt": "2026-05-01T08:33:19Z"
6
  }
css/style.css CHANGED
@@ -1723,6 +1723,50 @@ a:hover { color: var(--info); }
1723
  .run-device-row-value { color: var(--foreground); font-weight: 600; }
1724
  .run-device-note { font-size: 11px; color: var(--foreground-muted); margin-top: 2px; }
1725
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1726
  /* Hide filters + iterations + action buttons — stacks with .filter-bar tokens. */
1727
  .run-filters { align-items: center; }
1728
  .run-filters-checks {
 
1723
  .run-device-row-value { color: var(--foreground); font-weight: 600; }
1724
  .run-device-note { font-size: 11px; color: var(--foreground-muted); margin-top: 2px; }
1725
 
1726
+ /* User-reported machine card — appears between the auto-detected device
1727
+ stats and the filter bar. <details>/<summary> so it can collapse once
1728
+ the user has filled it in. */
1729
+ .run-machine-card { margin-bottom: 24px; }
1730
+ .run-machine-card > summary { cursor: pointer; }
1731
+ .run-machine-card > summary > strong { font-weight: 600; }
1732
+ .run-machine-grid {
1733
+ display: grid;
1734
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
1735
+ gap: 12px;
1736
+ margin-top: 12px;
1737
+ }
1738
+ .run-machine-field { display: flex; flex-direction: column; gap: 6px; }
1739
+ .run-machine-label {
1740
+ font-size: 12px;
1741
+ color: var(--foreground-muted);
1742
+ font-weight: 500;
1743
+ }
1744
+ .run-machine-req { color: var(--danger, #d04545); font-weight: 700; }
1745
+ .run-machine-opt { color: var(--foreground-muted); font-weight: 400; }
1746
+ .run-machine-input {
1747
+ height: 34px;
1748
+ padding: 0 10px;
1749
+ background: var(--surface-1);
1750
+ border: 1px solid var(--border);
1751
+ border-radius: var(--radius-md);
1752
+ color: var(--foreground);
1753
+ font-family: var(--font-mono);
1754
+ font-size: 13px;
1755
+ }
1756
+ .run-machine-input:focus {
1757
+ outline: none;
1758
+ border-color: var(--brand, #2b6df0);
1759
+ }
1760
+ .run-machine-input.is-missing {
1761
+ border-color: var(--danger, #d04545);
1762
+ }
1763
+ .run-machine-hint {
1764
+ margin-top: 10px;
1765
+ font-size: 11px;
1766
+ color: var(--foreground-muted);
1767
+ }
1768
+ .run-machine-hint.is-warn { color: var(--danger, #d04545); }
1769
+
1770
  /* Hide filters + iterations + action buttons — stacks with .filter-bar tokens. */
1771
  .run-filters { align-items: center; }
1772
  .run-filters-checks {
js/run/bench-worker.js CHANGED
@@ -340,6 +340,22 @@ async function runOne({ params, opfsPath }) {
340
  if (loadResult !== 0) throw new Error(`bench_load failed: ${loadResult}`);
341
  log('Model loaded');
342
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
343
  // ─── Consistency phase ───
344
  // Soft-fail: a failure here logs and falls through to the perf phase
345
  // rather than aborting the whole run. Some devices/models can't survive
 
340
  if (loadResult !== 0) throw new Error(`bench_load failed: ${loadResult}`);
341
  log('Model loaded');
342
 
343
+ // ─── Memory snapshot from llama.cpp ───
344
+ // Captured immediately after bench_load so model_size reflects the loaded
345
+ // model and per-device free counters reflect post-allocation state. Wrapped
346
+ // in try/catch — if the C side or a backend errors, the run can still
347
+ // produce perf numbers, just without memoryInfo on the record.
348
+ try {
349
+ const raw = await Module.ccall('bench_memory_info', 'string', [], [], { async: true });
350
+ result.memoryInfo = parseBenchResult('bench_memory_info', raw);
351
+ const dev = (result.memoryInfo.devices || [])
352
+ .map(d => `${d.name}(${d.type}) free=${(d.free / (1024 * 1024)).toFixed(0)}MB total=${(d.total / (1024 * 1024)).toFixed(0)}MB`)
353
+ .join(' | ') || 'none';
354
+ log(`Memory: model=${(result.memoryInfo.model_size / (1024 * 1024)).toFixed(0)}MB state=${(result.memoryInfo.state_size / (1024 * 1024)).toFixed(0)}MB | ${dev}`);
355
+ } catch (err) {
356
+ log(`bench_memory_info failed: ${err.message} — continuing without memoryInfo`);
357
+ }
358
+
359
  // ─── Consistency phase ───
360
  // Soft-fail: a failure here logs and falls through to the perf phase
361
  // rather than aborting the whole run. Some devices/models can't survive
js/run/controller.js CHANGED
@@ -12,6 +12,7 @@ import {
12
  import { isHubConfigured, HF_DATASET_REPO, CONSISTENCY_PROMPT } from './config.js';
13
 
14
  const RUN_INTENT_STORAGE_KEY = 'webgpu-bench:runIntent';
 
15
  const CRASH_STALE_MS = 10_000;
16
 
17
  const DEFAULT_N_PREDICT = 128;
@@ -62,8 +63,33 @@ const state = {
62
  // versions. JSPI and Asyncify variants are built from the same source
63
  // tree, so a single fetch is enough; both files would be identical.
64
  buildInfo: null,
 
 
 
 
 
 
 
65
  };
66
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  // Register an abort callback for an in-flight async op (worker terminate,
68
  // fetch signal abort, etc.). Returns an unregister fn the caller MUST
69
  // invoke when the op settles, so we don't accumulate stale handlers across
@@ -943,6 +969,91 @@ function slugify(s) {
943
  return String(s).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'unknown';
944
  }
945
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
946
  async function machineInfo() {
947
  const ua = navigator.userAgent;
948
  const platform = /Mac/.test(ua) ? 'darwin'
@@ -1511,6 +1622,15 @@ function makeRecord(v, vr, machine, browser, wallTimeMs) {
1511
  cpu_baseline: cpuBaseline,
1512
  output: gpu?.output || '',
1513
  machine,
 
 
 
 
 
 
 
 
 
1514
  source: `webgpu-bench/site (${state.surface})`,
1515
  };
1516
  }
@@ -1754,6 +1874,17 @@ function wireHubHandlers() {
1754
  if (!state.hfSession) return;
1755
  const eligible = submittableResults();
1756
  if (eligible.length === 0) return;
 
 
 
 
 
 
 
 
 
 
 
1757
  submitBtn.disabled = true;
1758
  const original = submitBtn.textContent;
1759
  submitBtn.textContent = 'Submitting…';
@@ -1845,6 +1976,7 @@ export async function mountRunSection() {
1845
  wirePurgeHandler();
1846
  wireHubHandlers();
1847
  wireOutputHandlers();
 
1848
  // Restore the last completed run from localStorage so it survives a page
1849
  // reload — including the OAuth redirect taking the user to HF and back.
1850
  // Must run before updateButtons/renderOutput/hideProgress so they pick up
 
12
  import { isHubConfigured, HF_DATASET_REPO, CONSISTENCY_PROMPT } from './config.js';
13
 
14
  const RUN_INTENT_STORAGE_KEY = 'webgpu-bench:runIntent';
15
+ const USER_REPORTED_STORAGE_KEY = 'webgpu-bench:userReported';
16
  const CRASH_STALE_MS = 10_000;
17
 
18
  const DEFAULT_N_PREDICT = 128;
 
63
  // versions. JSPI and Asyncify variants are built from the same source
64
  // tree, so a single fetch is enough; both files would be identical.
65
  buildInfo: null,
66
+ // User-reported machine identity (Machine Name / GPU Name / Browser /
67
+ // OS). Filled by the "Your machine" form on the Run page, persisted to
68
+ // localStorage between visits, and stamped onto every result record so
69
+ // the leaderboard can attribute submissions even when UA / WebGPU
70
+ // adapter info is missing or wrong. machineName/browser/os are required
71
+ // before submission; gpuName is optional.
72
+ userReported: { machineName: '', gpuName: '', browser: '', os: '' },
73
  };
74
 
75
+ const USER_REPORTED_REQUIRED = ['machineName', 'browser', 'os'];
76
+
77
+ function loadUserReported() {
78
+ try {
79
+ const raw = localStorage.getItem(USER_REPORTED_STORAGE_KEY);
80
+ if (!raw) return null;
81
+ const parsed = JSON.parse(raw);
82
+ if (parsed && typeof parsed === 'object') return parsed;
83
+ } catch { /* corrupt storage */ }
84
+ return null;
85
+ }
86
+
87
+ function saveUserReported() {
88
+ try {
89
+ localStorage.setItem(USER_REPORTED_STORAGE_KEY, JSON.stringify(state.userReported));
90
+ } catch { /* quota / disabled */ }
91
+ }
92
+
93
  // Register an abort callback for an in-flight async op (worker terminate,
94
  // fetch signal abort, etc.). Returns an unregister fn the caller MUST
95
  // invoke when the op settles, so we don't accumulate stale handlers across
 
969
  return String(s).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'unknown';
970
  }
971
 
972
+ // ──────────────── user-reported submission fields ────────────────
973
+
974
+ // Best-effort default for the four user-reported inputs, derived from the
975
+ // auto-detected device + browser data. The user is expected to edit these
976
+ // before running — defaults exist only so the form isn't empty on first
977
+ // visit. Returns { machineName, gpuName, browser, os }.
978
+ function autoDetectedUserReported() {
979
+ const d = state.device || {};
980
+ const gpu = d.gpu || {};
981
+ const gpuStr = [gpu.vendor, gpu.architecture, gpu.device, gpu.description]
982
+ .filter(Boolean).join(' ').trim();
983
+ const memGB = state.budget?.memGB;
984
+ const browser = formatBrowser(d);
985
+ const os = formatPlatform(d);
986
+ // machineName default: "<gpu> · <memGB> GB" if both known, else either,
987
+ // else the OS string. The user is encouraged to replace with a friendly
988
+ // label like "MacBook Pro M3 16GB".
989
+ let machineName = '';
990
+ if (gpuStr && memGB) machineName = `${gpuStr} · ${memGB} GB`;
991
+ else if (gpuStr) machineName = gpuStr;
992
+ else if (memGB) machineName = `${memGB} GB device`;
993
+ else machineName = os;
994
+ return { machineName, gpuName: gpuStr, browser, os };
995
+ }
996
+
997
+ function readUserReportedFromInputs() {
998
+ return {
999
+ machineName: ($('ur-machine-name')?.value ?? '').trim(),
1000
+ gpuName: ($('ur-gpu-name')?.value ?? '').trim(),
1001
+ browser: ($('ur-browser')?.value ?? '').trim(),
1002
+ os: ($('ur-os')?.value ?? '').trim(),
1003
+ };
1004
+ }
1005
+
1006
+ function refreshUserReportedValidation() {
1007
+ const hint = $('ur-hint');
1008
+ const missing = USER_REPORTED_REQUIRED.filter(k => !state.userReported[k]);
1009
+ for (const k of USER_REPORTED_REQUIRED) {
1010
+ const id = { machineName: 'ur-machine-name', browser: 'ur-browser', os: 'ur-os' }[k];
1011
+ const el = $(id);
1012
+ if (el) el.classList.toggle('is-missing', !state.userReported[k]);
1013
+ }
1014
+ if (hint) {
1015
+ if (missing.length === 0) {
1016
+ hint.textContent = 'Looks good — these labels will be attached to every result you submit.';
1017
+ hint.classList.remove('is-warn');
1018
+ } else {
1019
+ hint.textContent = `Required: ${missing.join(', ')}. We'll still let you run, but submissions need these filled in.`;
1020
+ hint.classList.add('is-warn');
1021
+ }
1022
+ }
1023
+ }
1024
+
1025
+ function wireUserReported() {
1026
+ // Pre-fill: stored values win, fall back to auto-detected defaults so
1027
+ // first-time users see something rather than an empty form.
1028
+ const stored = loadUserReported();
1029
+ const auto = autoDetectedUserReported();
1030
+ state.userReported = {
1031
+ machineName: stored?.machineName?.trim() || auto.machineName,
1032
+ gpuName: stored?.gpuName?.trim() || auto.gpuName,
1033
+ browser: stored?.browser?.trim() || auto.browser,
1034
+ os: stored?.os?.trim() || auto.os,
1035
+ };
1036
+ for (const [id, key] of [
1037
+ ['ur-machine-name', 'machineName'],
1038
+ ['ur-gpu-name', 'gpuName'],
1039
+ ['ur-browser', 'browser'],
1040
+ ['ur-os', 'os'],
1041
+ ]) {
1042
+ const el = $(id);
1043
+ if (!el) continue;
1044
+ el.value = state.userReported[key] || '';
1045
+ el.addEventListener('input', () => {
1046
+ state.userReported = readUserReportedFromInputs();
1047
+ saveUserReported();
1048
+ refreshUserReportedValidation();
1049
+ });
1050
+ }
1051
+ // Persist whatever the auto-detect filled in so the user doesn't lose
1052
+ // it on reload before they touch anything.
1053
+ saveUserReported();
1054
+ refreshUserReportedValidation();
1055
+ }
1056
+
1057
  async function machineInfo() {
1058
  const ua = navigator.userAgent;
1059
  const platform = /Mac/.test(ua) ? 'darwin'
 
1622
  cpu_baseline: cpuBaseline,
1623
  output: gpu?.output || '',
1624
  machine,
1625
+ // Memory snapshot llama.cpp captured immediately after bench_load —
1626
+ // model_size, state_size, and per-device {free,total} from every ggml
1627
+ // backend. Useful for spotting memory-pressured runs and for sanity-
1628
+ // checking GPU memory headroom across machines.
1629
+ memoryInfo: gpu?.memoryInfo ?? null,
1630
+ // User-typed labels that override (or supplement) the auto-detected
1631
+ // machine/browser fields. Auto-detection is unreliable across UA-string
1632
+ // anonymization, deviceMemory rounding, and missing WebGPU adapter info.
1633
+ userReported: { ...state.userReported },
1634
  source: `webgpu-bench/site (${state.surface})`,
1635
  };
1636
  }
 
1874
  if (!state.hfSession) return;
1875
  const eligible = submittableResults();
1876
  if (eligible.length === 0) return;
1877
+ // Required user-reported fields gate the submission so the leaderboard
1878
+ // doesn't accumulate anonymous rows. The Run buttons stay enabled
1879
+ // even when these are blank — we only block at submit time.
1880
+ const missing = USER_REPORTED_REQUIRED.filter(k => !state.userReported[k]);
1881
+ if (missing.length > 0) {
1882
+ const card = $('user-reported-card');
1883
+ if (card) { card.open = true; card.scrollIntoView({ behavior: 'smooth', block: 'center' }); }
1884
+ refreshUserReportedValidation();
1885
+ logLine(`Submit blocked: fill in ${missing.join(', ')} in "Your machine".`);
1886
+ return;
1887
+ }
1888
  submitBtn.disabled = true;
1889
  const original = submitBtn.textContent;
1890
  submitBtn.textContent = 'Submitting…';
 
1976
  wirePurgeHandler();
1977
  wireHubHandlers();
1978
  wireOutputHandlers();
1979
+ wireUserReported();
1980
  // Restore the last completed run from localStorage so it survives a page
1981
  // reload — including the OAuth redirect taking the user to HF and back.
1982
  // Must run before updateButtons/renderOutput/hideProgress so they pick up
run.html CHANGED
@@ -166,6 +166,37 @@
166
  </div>
167
  </div>
168
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  <!-- Hide filters, iterations, actions -->
170
  <div class="filter-bar run-controls">
171
  <div class="filter-bar-inner run-filters">
 
166
  </div>
167
  </div>
168
 
169
+ <!-- User-reported machine identity. The auto-detected values in the
170
+ cards above are unreliable (UA strings lie, deviceMemory is
171
+ coarse, GPU adapter info is often empty). We ship these
172
+ user-typed fields alongside the auto-detected ones so the
173
+ leaderboard can attribute submissions correctly. Persisted to
174
+ localStorage between visits. -->
175
+ <details class="card run-machine-card" id="user-reported-card" open>
176
+ <summary>
177
+ <strong>Your machine</strong> — labels the auto-detected device data on submission. Saved between visits.
178
+ </summary>
179
+ <div class="run-machine-grid">
180
+ <label class="run-machine-field">
181
+ <span class="run-machine-label">Machine name <span class="run-machine-req" aria-hidden="true">*</span></span>
182
+ <input type="text" id="ur-machine-name" class="run-machine-input" placeholder="e.g. MacBook Pro M3 16GB" autocomplete="off" spellcheck="false">
183
+ </label>
184
+ <label class="run-machine-field">
185
+ <span class="run-machine-label">GPU name <span class="run-machine-opt">(optional)</span></span>
186
+ <input type="text" id="ur-gpu-name" class="run-machine-input" placeholder="e.g. Apple M3 Pro" autocomplete="off" spellcheck="false">
187
+ </label>
188
+ <label class="run-machine-field">
189
+ <span class="run-machine-label">Browser <span class="run-machine-req" aria-hidden="true">*</span></span>
190
+ <input type="text" id="ur-browser" class="run-machine-input" placeholder="e.g. Chrome 138 dev" autocomplete="off" spellcheck="false">
191
+ </label>
192
+ <label class="run-machine-field">
193
+ <span class="run-machine-label">Operating system <span class="run-machine-req" aria-hidden="true">*</span></span>
194
+ <input type="text" id="ur-os" class="run-machine-input" placeholder="e.g. macOS 15.4" autocomplete="off" spellcheck="false">
195
+ </label>
196
+ </div>
197
+ <p class="run-machine-hint" id="ur-hint">Required fields marked <span class="run-machine-req">*</span>. Defaults are filled in from your browser; edit anything that's wrong before running.</p>
198
+ </details>
199
+
200
  <!-- Hide filters, iterations, actions -->
201
  <div class="filter-bar run-controls">
202
  <div class="filter-bar-inner run-filters">