Mike0021 commited on
Commit
6076335
·
verified ·
1 Parent(s): c750135

Add WebGPU evidence row action

Browse files
Files changed (4) hide show
  1. README.md +1 -1
  2. app.js +86 -2
  3. index.html +1 -0
  4. styles.css +2 -0
README.md CHANGED
@@ -28,7 +28,7 @@ Known testing notes:
28
  - The app automatically falls back to WASM if hardware WebGPU is unavailable.
29
  - Initial load can be large because STT, LLM, TTS, and voice embeddings are all browser-side assets.
30
  - For validation evidence, run **Run benchmark suite**, **Run 3 real-mic series**, then **Download JSON**.
31
- - The Latency panel shows separate real-mic and hardware-WebGPU validation cards.
32
  - The exported `hostMetadata.hfSpaceCommit` identifies the exact static Space revision served to the browser.
33
  - Hosted smoke validation passed on May 28, 2026: TTS, identity, and loopback rows completed with zero benchmark-phase network requests, zero benchmark errors, and zero server-inference suspects.
34
  - The latest local validation also passed UI smoke, client-side/no-server smoke, synthetic loopback stability, and first-TTS-chunk safety. The remaining external gates are real human microphone rows and a hardware WebGPU row from a browser that exposes a real GPU adapter.
 
28
  - The app automatically falls back to WASM if hardware WebGPU is unavailable.
29
  - Initial load can be large because STT, LLM, TTS, and voice embeddings are all browser-side assets.
30
  - For validation evidence, run **Run benchmark suite**, **Run 3 real-mic series**, then **Download JSON**.
31
+ - The Latency panel shows separate real-mic and hardware-WebGPU validation cards. On hardware browsers, **Run WebGPU evidence row** captures a fast row for the downloaded JSON.
32
  - The exported `hostMetadata.hfSpaceCommit` identifies the exact static Space revision served to the browser.
33
  - Hosted smoke validation passed on May 28, 2026: TTS, identity, and loopback rows completed with zero benchmark-phase network requests, zero benchmark errors, and zero server-inference suspects.
34
  - The latest local validation also passed UI smoke, client-side/no-server smoke, synthetic loopback stability, and first-TTS-chunk safety. The remaining external gates are real human microphone rows and a hardware WebGPU row from a browser that exposes a real GPU adapter.
app.js CHANGED
@@ -10,6 +10,7 @@ const elements = {
10
  ttsBenchmarkButton: $("ttsBenchmarkButton"),
11
  loopbackButton: $("loopbackButton"),
12
  bargeInButton: $("bargeInButton"),
 
13
  micBenchmarkButton: $("micBenchmarkButton"),
14
  micSeriesButton: $("micSeriesButton"),
15
  copyResultsButton: $("copyResultsButton"),
@@ -88,6 +89,14 @@ const IDENTITY_PRIMER_MESSAGES = [
88
  role: "assistant",
89
  content: APP_IDENTITY_ANSWER,
90
  },
 
 
 
 
 
 
 
 
91
  ];
92
  const APP_IDENTITY_QUALITY_RULES = [
93
  { label: "client/browser/local", pattern: /\b(client(?:-side)?|browser|local)\b/i },
@@ -120,9 +129,18 @@ const MIN_VAD_SILENCE_MS = 200;
120
  const MAX_VAD_SILENCE_MS = 800;
121
  const DEFAULT_LOOPBACK_SPEED = 1.0;
122
  const DEFAULT_LOOPBACK_PREROLL_MS = 180;
123
- const DEFAULT_LOOPBACK_PROMPT = "What application is this?";
124
  const REAL_MIC_TARGET_RUNS = 3;
125
  const REAL_MIC_MAX_WER = 0.25;
 
 
 
 
 
 
 
 
 
126
  const MIC_START_TIMEOUT_MS = 15000;
127
  const WEBGPU_ADAPTER_TIMEOUT_MS = 5000;
128
  const DEFAULT_TTS_CHUNKING = Object.freeze({
@@ -469,6 +487,7 @@ function setBenchmarkControlsDisabled(disabled) {
469
  elements.bargeInButton.disabled = locked;
470
  elements.micBenchmarkButton.disabled = locked;
471
  elements.micSeriesButton.disabled = locked;
 
472
  }
473
 
474
  function resetMetrics() {
@@ -579,6 +598,21 @@ function updateGpuValidationStatus() {
579
  elements.gpuValidationStatus.textContent = status;
580
  elements.gpuValidationDetail.textContent = detail;
581
  elements.gpuValidationProgressBar.style.width = `${Math.round(Math.max(0, Math.min(1, progress)) * 100)}%`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
582
  }
583
 
584
  function resetTurnState() {
@@ -1356,7 +1390,11 @@ function updateOutputQuality(benchmark, output) {
1356
 
1357
  function isAppIdentityQuestion(text) {
1358
  const normalized = normalizeForQuality(text);
1359
- return /^(what's|what is|what app is|what demo is|what application is) this$/.test(normalized);
 
 
 
 
1360
  }
1361
 
1362
  function words(text) {
@@ -2302,6 +2340,48 @@ function runBargeInBenchmark() {
2302
  return true;
2303
  }
2304
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2305
  async function runMicBenchmark(options = {}) {
2306
  const series = options?.series === true;
2307
  if (!state.modelsLoaded) return false;
@@ -2690,6 +2770,7 @@ function installAutomationApi() {
2690
  runTts: (options) => runSingleAutomationBenchmark(runTtsBenchmark, options),
2691
  runLoopback: (options) => runSingleAutomationBenchmark(runLoopbackBenchmark, options),
2692
  runBargeIn: (options) => runSingleAutomationBenchmark(runBargeInBenchmark, options),
 
2693
  runMic: (options) => runSingleAutomationBenchmark(runMicBenchmark, options),
2694
  runMicSeries: runAutomationMicSeries,
2695
  synthesizeAudio: synthesizeAutomationAudio,
@@ -2734,6 +2815,9 @@ elements.chatBenchmarkButton.addEventListener("click", runChatBenchmark);
2734
  elements.ttsBenchmarkButton.addEventListener("click", runTtsBenchmark);
2735
  elements.loopbackButton.addEventListener("click", runLoopbackBenchmark);
2736
  elements.bargeInButton.addEventListener("click", runBargeInBenchmark);
 
 
 
2737
  elements.micBenchmarkButton.addEventListener("click", runMicBenchmark);
2738
  elements.micSeriesButton.addEventListener("click", runMicSeriesBenchmark);
2739
  elements.copyResultsButton.addEventListener("click", () => {
 
10
  ttsBenchmarkButton: $("ttsBenchmarkButton"),
11
  loopbackButton: $("loopbackButton"),
12
  bargeInButton: $("bargeInButton"),
13
+ gpuBenchmarkButton: $("gpuBenchmarkButton"),
14
  micBenchmarkButton: $("micBenchmarkButton"),
15
  micSeriesButton: $("micSeriesButton"),
16
  copyResultsButton: $("copyResultsButton"),
 
89
  role: "assistant",
90
  content: APP_IDENTITY_ANSWER,
91
  },
92
+ {
93
+ role: "user",
94
+ content: "Please identify this browser demo.",
95
+ },
96
+ {
97
+ role: "assistant",
98
+ content: APP_IDENTITY_ANSWER,
99
+ },
100
  ];
101
  const APP_IDENTITY_QUALITY_RULES = [
102
  { label: "client/browser/local", pattern: /\b(client(?:-side)?|browser|local)\b/i },
 
129
  const MAX_VAD_SILENCE_MS = 800;
130
  const DEFAULT_LOOPBACK_SPEED = 1.0;
131
  const DEFAULT_LOOPBACK_PREROLL_MS = 180;
132
+ const DEFAULT_LOOPBACK_PROMPT = "Please identify this browser demo.";
133
  const REAL_MIC_TARGET_RUNS = 3;
134
  const REAL_MIC_MAX_WER = 0.25;
135
+ const HARDWARE_WEBGPU_EVIDENCE_STACK = Object.freeze({
136
+ device: "webgpu",
137
+ llm: "HuggingFaceTB/SmolLM2-135M-Instruct",
138
+ asr: "onnx-community/moonshine-base-ONNX",
139
+ voice: "F2",
140
+ ttsSteps: 2,
141
+ vadSilenceMs: 480,
142
+ partialAsr: true,
143
+ });
144
  const MIC_START_TIMEOUT_MS = 15000;
145
  const WEBGPU_ADAPTER_TIMEOUT_MS = 5000;
146
  const DEFAULT_TTS_CHUNKING = Object.freeze({
 
487
  elements.bargeInButton.disabled = locked;
488
  elements.micBenchmarkButton.disabled = locked;
489
  elements.micSeriesButton.disabled = locked;
490
+ updateGpuEvidenceButton();
491
  }
492
 
493
  function resetMetrics() {
 
598
  elements.gpuValidationStatus.textContent = status;
599
  elements.gpuValidationDetail.textContent = detail;
600
  elements.gpuValidationProgressBar.style.width = `${Math.round(Math.max(0, Math.min(1, progress)) * 100)}%`;
601
+ updateGpuEvidenceButton();
602
+ }
603
+
604
+ function updateGpuEvidenceButton() {
605
+ if (!elements.gpuBenchmarkButton) return;
606
+ const hasHardware = state.webgpuAvailable === true && state.webgpuSoftwareAdapter !== true;
607
+ const busy =
608
+ state.modelsLoading ||
609
+ state.suiteRunning ||
610
+ state.micSeries.active ||
611
+ Boolean(state.activeBenchmark);
612
+ const hardwareRows = evidenceSummaryForRows(state.benchmarkResults).hardwareWebgpu.hardwareRows ?? 0;
613
+ elements.gpuBenchmarkButton.disabled = busy || !hasHardware;
614
+ elements.gpuBenchmarkButton.textContent =
615
+ hardwareRows > 0 ? "Run another WebGPU evidence row" : "Run WebGPU evidence row";
616
  }
617
 
618
  function resetTurnState() {
 
1390
 
1391
  function isAppIdentityQuestion(text) {
1392
  const normalized = normalizeForQuality(text);
1393
+ return (
1394
+ /^(what's|what is|what app is|what demo is|what application is) this$/.test(normalized) ||
1395
+ /^(what's|what is) this (app|application|demo)$/.test(normalized) ||
1396
+ /^(please )?identify this (browser )?(demo|app|application)$/.test(normalized)
1397
+ );
1398
  }
1399
 
1400
  function words(text) {
 
2340
  return true;
2341
  }
2342
 
2343
+ async function runHardwareWebGpuEvidenceBenchmark() {
2344
+ if (state.activeBenchmark || state.suiteRunning || state.micSeries.active || state.modelsLoading) {
2345
+ logEvent("Finish the current benchmark before collecting WebGPU evidence.");
2346
+ return false;
2347
+ }
2348
+ await supportsWebGPU();
2349
+ if (!state.webgpuAvailable || state.webgpuSoftwareAdapter) {
2350
+ logEvent("Hardware WebGPU evidence requires a browser exposing a real GPU adapter.");
2351
+ updateGpuValidationStatus();
2352
+ return false;
2353
+ }
2354
+ if (!state.modelsLoaded) {
2355
+ applyHardwareWebGpuEvidenceStack();
2356
+ updateRuntimeStatus();
2357
+ logEvent("Loading the default hardware WebGPU evidence stack.");
2358
+ await loadModels({ ttsWarmup: false });
2359
+ if (!state.modelsLoaded) {
2360
+ logEvent("WebGPU evidence stack did not finish loading.");
2361
+ return false;
2362
+ }
2363
+ }
2364
+ const stack = currentBenchmarkStack();
2365
+ if (stack.device !== "webgpu") {
2366
+ logEvent("Unload the current stack, select WebGPU or Auto on a hardware adapter, then rerun WebGPU evidence.");
2367
+ return false;
2368
+ }
2369
+ logEvent("Running hardware WebGPU evidence row.");
2370
+ return runBenchmark();
2371
+ }
2372
+
2373
+ function applyHardwareWebGpuEvidenceStack() {
2374
+ elements.deviceSelect.value = HARDWARE_WEBGPU_EVIDENCE_STACK.device;
2375
+ elements.llmModelSelect.value = HARDWARE_WEBGPU_EVIDENCE_STACK.llm;
2376
+ elements.asrModelSelect.value = HARDWARE_WEBGPU_EVIDENCE_STACK.asr;
2377
+ elements.voiceSelect.value = HARDWARE_WEBGPU_EVIDENCE_STACK.voice;
2378
+ elements.ttsSteps.value = String(HARDWARE_WEBGPU_EVIDENCE_STACK.ttsSteps);
2379
+ elements.vadSilence.value = String(HARDWARE_WEBGPU_EVIDENCE_STACK.vadSilenceMs);
2380
+ elements.partialToggle.checked = HARDWARE_WEBGPU_EVIDENCE_STACK.partialAsr;
2381
+ updateTtsStepsLabel();
2382
+ updateVadSilenceLabel();
2383
+ }
2384
+
2385
  async function runMicBenchmark(options = {}) {
2386
  const series = options?.series === true;
2387
  if (!state.modelsLoaded) return false;
 
2770
  runTts: (options) => runSingleAutomationBenchmark(runTtsBenchmark, options),
2771
  runLoopback: (options) => runSingleAutomationBenchmark(runLoopbackBenchmark, options),
2772
  runBargeIn: (options) => runSingleAutomationBenchmark(runBargeInBenchmark, options),
2773
+ runWebGpuEvidence: (options) => runSingleAutomationBenchmark(runHardwareWebGpuEvidenceBenchmark, options),
2774
  runMic: (options) => runSingleAutomationBenchmark(runMicBenchmark, options),
2775
  runMicSeries: runAutomationMicSeries,
2776
  synthesizeAudio: synthesizeAutomationAudio,
 
2815
  elements.ttsBenchmarkButton.addEventListener("click", runTtsBenchmark);
2816
  elements.loopbackButton.addEventListener("click", runLoopbackBenchmark);
2817
  elements.bargeInButton.addEventListener("click", runBargeInBenchmark);
2818
+ elements.gpuBenchmarkButton.addEventListener("click", () => {
2819
+ runHardwareWebGpuEvidenceBenchmark().catch((error) => logEvent(`WebGPU evidence failed: ${error.message}`));
2820
+ });
2821
  elements.micBenchmarkButton.addEventListener("click", runMicBenchmark);
2822
  elements.micSeriesButton.addEventListener("click", runMicSeriesBenchmark);
2823
  elements.copyResultsButton.addEventListener("click", () => {
index.html CHANGED
@@ -190,6 +190,7 @@
190
  <button id="ttsBenchmarkButton" type="button" disabled>Run TTS benchmark</button>
191
  <button id="loopbackButton" type="button" disabled>Run voice loopback</button>
192
  <button id="bargeInButton" type="button" disabled>Run barge-in check</button>
 
193
  <button id="micBenchmarkButton" type="button" disabled>Benchmark real mic</button>
194
  <button id="micSeriesButton" type="button" disabled>Run 3 real-mic series</button>
195
  </section>
 
190
  <button id="ttsBenchmarkButton" type="button" disabled>Run TTS benchmark</button>
191
  <button id="loopbackButton" type="button" disabled>Run voice loopback</button>
192
  <button id="bargeInButton" type="button" disabled>Run barge-in check</button>
193
+ <button id="gpuBenchmarkButton" type="button" disabled>Run WebGPU evidence row</button>
194
  <button id="micBenchmarkButton" type="button" disabled>Benchmark real mic</button>
195
  <button id="micSeriesButton" type="button" disabled>Run 3 real-mic series</button>
196
  </section>
styles.css CHANGED
@@ -426,6 +426,7 @@ h2 {
426
  #ttsBenchmarkButton,
427
  #loopbackButton,
428
  #bargeInButton,
 
429
  #micBenchmarkButton,
430
  #micSeriesButton {
431
  width: 100%;
@@ -436,6 +437,7 @@ h2 {
436
  #ttsBenchmarkButton,
437
  #loopbackButton,
438
  #bargeInButton,
 
439
  #micBenchmarkButton,
440
  #micSeriesButton {
441
  margin-top: 8px;
 
426
  #ttsBenchmarkButton,
427
  #loopbackButton,
428
  #bargeInButton,
429
+ #gpuBenchmarkButton,
430
  #micBenchmarkButton,
431
  #micSeriesButton {
432
  width: 100%;
 
437
  #ttsBenchmarkButton,
438
  #loopbackButton,
439
  #bargeInButton,
440
+ #gpuBenchmarkButton,
441
  #micBenchmarkButton,
442
  #micSeriesButton {
443
  margin-top: 8px;