Spaces:
Running
Running
Add hardware WebGPU validation card
Browse files- README.md +1 -0
- app.js +41 -0
- index.html +8 -0
README.md
CHANGED
|
@@ -28,6 +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 exported `hostMetadata.hfSpaceCommit` identifies the exact static Space revision served to the browser.
|
| 32 |
- 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.
|
| 33 |
- 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.
|
| 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
|
@@ -41,6 +41,10 @@ const elements = {
|
|
| 41 |
micValidationStatus: $("micValidationStatus"),
|
| 42 |
micValidationDetail: $("micValidationDetail"),
|
| 43 |
micValidationProgressBar: $("micValidationProgressBar"),
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
vadCloseLatency: $("vadCloseLatency"),
|
| 45 |
asrLatency: $("asrLatency"),
|
| 46 |
firstTokenLatency: $("firstTokenLatency"),
|
|
@@ -350,6 +354,7 @@ function updateRuntimeStatus() {
|
|
| 350 |
elements.runtimeStatus.dataset.state = mode;
|
| 351 |
elements.runtimeDeviceStatus.textContent = status;
|
| 352 |
elements.runtimeDeviceDetail.textContent = detail;
|
|
|
|
| 353 |
}
|
| 354 |
|
| 355 |
function formatMs(value) {
|
|
@@ -538,6 +543,42 @@ function updateMicValidationStatus() {
|
|
| 538 |
elements.micValidationStatus.textContent = status;
|
| 539 |
elements.micValidationDetail.textContent = detail;
|
| 540 |
elements.micValidationProgressBar.style.width = `${Math.round(Math.max(0, Math.min(1, progress)) * 100)}%`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 541 |
}
|
| 542 |
|
| 543 |
function resetTurnState() {
|
|
|
|
| 41 |
micValidationStatus: $("micValidationStatus"),
|
| 42 |
micValidationDetail: $("micValidationDetail"),
|
| 43 |
micValidationProgressBar: $("micValidationProgressBar"),
|
| 44 |
+
gpuValidationCard: $("gpuValidationCard"),
|
| 45 |
+
gpuValidationStatus: $("gpuValidationStatus"),
|
| 46 |
+
gpuValidationDetail: $("gpuValidationDetail"),
|
| 47 |
+
gpuValidationProgressBar: $("gpuValidationProgressBar"),
|
| 48 |
vadCloseLatency: $("vadCloseLatency"),
|
| 49 |
asrLatency: $("asrLatency"),
|
| 50 |
firstTokenLatency: $("firstTokenLatency"),
|
|
|
|
| 354 |
elements.runtimeStatus.dataset.state = mode;
|
| 355 |
elements.runtimeDeviceStatus.textContent = status;
|
| 356 |
elements.runtimeDeviceDetail.textContent = detail;
|
| 357 |
+
updateGpuValidationStatus();
|
| 358 |
}
|
| 359 |
|
| 360 |
function formatMs(value) {
|
|
|
|
| 543 |
elements.micValidationStatus.textContent = status;
|
| 544 |
elements.micValidationDetail.textContent = detail;
|
| 545 |
elements.micValidationProgressBar.style.width = `${Math.round(Math.max(0, Math.min(1, progress)) * 100)}%`;
|
| 546 |
+
updateGpuValidationStatus();
|
| 547 |
+
}
|
| 548 |
+
|
| 549 |
+
function updateGpuValidationStatus() {
|
| 550 |
+
if (!elements.gpuValidationCard) return;
|
| 551 |
+
const evidence = evidenceSummaryForRows(state.benchmarkResults).hardwareWebgpu;
|
| 552 |
+
const hardwareRows = evidence.hardwareRows ?? 0;
|
| 553 |
+
let mode = "idle";
|
| 554 |
+
let status = "Checking adapter";
|
| 555 |
+
let detail = "Adapter probe pending.";
|
| 556 |
+
let progress = 0;
|
| 557 |
+
|
| 558 |
+
if (hardwareRows > 0) {
|
| 559 |
+
mode = "ready";
|
| 560 |
+
status = `${hardwareRows} hardware WebGPU row${hardwareRows === 1 ? "" : "s"}`;
|
| 561 |
+
detail = evidence.adapters[0]?.label ?? "Hardware adapter recorded in benchmark rows.";
|
| 562 |
+
progress = 1;
|
| 563 |
+
} else if (state.webgpuAvailable === false) {
|
| 564 |
+
mode = "warn";
|
| 565 |
+
status = "No WebGPU adapter";
|
| 566 |
+
detail = "Hardware WebGPU is not exposed in this browser.";
|
| 567 |
+
} else if (state.webgpuAvailable === true && state.webgpuSoftwareAdapter) {
|
| 568 |
+
mode = "warn";
|
| 569 |
+
status = "Software WebGPU only";
|
| 570 |
+
detail = `Software adapter: ${formatGpuAdapter(state.webgpuAdapterInfo)}`;
|
| 571 |
+
} else if (state.webgpuAvailable === true) {
|
| 572 |
+
mode = "warn";
|
| 573 |
+
status = "Hardware WebGPU available";
|
| 574 |
+
detail = `${formatGpuAdapter(state.webgpuAdapterInfo)}. Run a WebGPU stack to capture a row.`;
|
| 575 |
+
progress = 0.5;
|
| 576 |
+
}
|
| 577 |
+
|
| 578 |
+
elements.gpuValidationCard.dataset.state = mode;
|
| 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() {
|
index.html
CHANGED
|
@@ -176,6 +176,14 @@
|
|
| 176 |
<span id="micValidationProgressBar"></span>
|
| 177 |
</div>
|
| 178 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
<button id="suiteButton" type="button" disabled>Run benchmark suite</button>
|
| 180 |
<button id="benchmarkButton" type="button" disabled>Run identity benchmark</button>
|
| 181 |
<button id="chatBenchmarkButton" type="button" disabled>Run chat benchmark</button>
|
|
|
|
| 176 |
<span id="micValidationProgressBar"></span>
|
| 177 |
</div>
|
| 178 |
</div>
|
| 179 |
+
<div id="gpuValidationCard" class="mic-validation" data-state="idle" aria-live="polite">
|
| 180 |
+
<span>Hardware WebGPU validation</span>
|
| 181 |
+
<strong id="gpuValidationStatus">Checking adapter</strong>
|
| 182 |
+
<small id="gpuValidationDetail">Adapter probe pending.</small>
|
| 183 |
+
<div class="mic-progress" aria-hidden="true">
|
| 184 |
+
<span id="gpuValidationProgressBar"></span>
|
| 185 |
+
</div>
|
| 186 |
+
</div>
|
| 187 |
<button id="suiteButton" type="button" disabled>Run benchmark suite</button>
|
| 188 |
<button id="benchmarkButton" type="button" disabled>Run identity benchmark</button>
|
| 189 |
<button id="chatBenchmarkButton" type="button" disabled>Run chat benchmark</button>
|