Spaces:
Running
Running
| import { useEffect, useMemo, useRef, useState } from 'react'; | |
| import { | |
| BrowserEngineClient, | |
| EngineClientError, | |
| evaluateModelContextPolicy, | |
| evaluateModelGate, | |
| loadModelManifestV2, | |
| type BenchmarkFlashMode, | |
| type BenchmarkKvCacheType, | |
| type BenchmarkWasmFlavor, | |
| type EngineCapabilities, | |
| type GenerateParams, | |
| type ModelManifestV2, | |
| type ModelTierId, | |
| type RequestedBackend, | |
| } from '../engine'; | |
| import { formatBytes } from '../lib/format'; | |
| import { | |
| BENCHMARK_WORKLOADS, | |
| DEFAULT_BENCHMARK_WORKLOAD_ID, | |
| benchmarkReportFilename, | |
| buildBenchmarkReport, | |
| serializeBenchmarkReport, | |
| type BenchmarkReport, | |
| type BenchmarkWorkloadId, | |
| type TeacherForcedReferenceScoreEvidence, | |
| } from './report'; | |
| import { | |
| STATE_DRIFT_REFERENCE_PATH, | |
| validateStateDriftReferenceFixture, | |
| } from './state-drift-reference'; | |
| const MANIFEST_PATH = 'manifest/models.json'; | |
| const DEFAULT_MODEL: ModelTierId = '1_7b'; | |
| const MAX_GLUE_INT = 2_147_483_647; | |
| interface RunProgress { | |
| label: string; | |
| loadedBytes: number; | |
| totalBytes: number; | |
| } | |
| let sharedBenchClient: BrowserEngineClient | null = null; | |
| function getBenchClient(): BrowserEngineClient { | |
| sharedBenchClient ??= new BrowserEngineClient(); | |
| return sharedBenchClient; | |
| } | |
| function manifestUrl(): string { | |
| return new URL(`${import.meta.env.BASE_URL}${MANIFEST_PATH}`, document.baseURI).href; | |
| } | |
| function stateDriftReferenceUrl(): string { | |
| return new URL( | |
| `${import.meta.env.BASE_URL}${STATE_DRIFT_REFERENCE_PATH}`, | |
| document.baseURI, | |
| ).href; | |
| } | |
| function errorMessage(error: unknown): string { | |
| if (error instanceof EngineClientError) return `${error.code}: ${error.message}`; | |
| return error instanceof Error ? error.message : String(error); | |
| } | |
| function isAbort(error: unknown, signal: AbortSignal): boolean { | |
| return signal.aborted | |
| || (error instanceof DOMException && error.name === 'AbortError') | |
| || (error instanceof EngineClientError && ['ABORTED', 'SHARD_DOWNLOAD_ABORTED'].includes(error.code)); | |
| } | |
| function throwIfAborted(signal: AbortSignal): void { | |
| if (signal.aborted) throw new DOMException('Benchmark stopped by operator.', 'AbortError'); | |
| } | |
| function parseOptionalInteger(value: string): number | null { | |
| if (value.trim() === '') return null; | |
| const parsed = Number(value); | |
| return Number.isSafeInteger(parsed) && parsed > 0 && parsed <= MAX_GLUE_INT ? parsed : null; | |
| } | |
| function formatDuration(value: number | null | undefined): string { | |
| if (value === null || value === undefined) return '—'; | |
| return value >= 1_000 ? `${(value / 1_000).toFixed(2)} s` : `${value.toFixed(1)} ms`; | |
| } | |
| function formatRate(value: number | null | undefined): string { | |
| return value === null || value === undefined ? '—' : `${value.toFixed(2)} t/s`; | |
| } | |
| function downloadReport(report: BenchmarkReport): void { | |
| const blob = new Blob([serializeBenchmarkReport(report)], { type: 'application/json' }); | |
| const url = URL.createObjectURL(blob); | |
| const anchor = document.createElement('a'); | |
| anchor.href = url; | |
| anchor.download = benchmarkReportFilename(report); | |
| document.body.append(anchor); | |
| anchor.click(); | |
| anchor.remove(); | |
| window.setTimeout(() => URL.revokeObjectURL(url), 0); | |
| } | |
| export function BenchApp() { | |
| const [client] = useState(getBenchClient); | |
| const [manifest, setManifest] = useState<ModelManifestV2 | null>(null); | |
| const [capabilities, setCapabilities] = useState<EngineCapabilities | null>(null); | |
| const [workloadId, setWorkloadId] = useState<BenchmarkWorkloadId>(DEFAULT_BENCHMARK_WORKLOAD_ID); | |
| const [modelId, setModelId] = useState<ModelTierId>(DEFAULT_MODEL); | |
| const [backend, setBackend] = useState<RequestedBackend>('auto'); | |
| const [contextSize, setContextSize] = useState(4_096); | |
| const [maxTokens, setMaxTokens] = useState(64); | |
| const [nBatchInput, setNBatchInput] = useState(''); | |
| const [nUbatchInput, setNUbatchInput] = useState(''); | |
| const [flashMode, setFlashMode] = useState<BenchmarkFlashMode>('off'); | |
| const [kvCacheType, setKvCacheType] = useState<BenchmarkKvCacheType>('f16'); | |
| const [wasmFlavor, setWasmFlavor] = useState<BenchmarkWasmFlavor>('auto'); | |
| const [running, setRunning] = useState(false); | |
| const [progress, setProgress] = useState<RunProgress>({ | |
| label: 'Waiting for an operator-initiated run', | |
| loadedBytes: 0, | |
| totalBytes: 0, | |
| }); | |
| const [report, setReport] = useState<BenchmarkReport | null>(null); | |
| const [output, setOutput] = useState(''); | |
| const [bootstrapError, setBootstrapError] = useState<string | null>(null); | |
| const [runError, setRunError] = useState<string | null>(null); | |
| const operationRef = useRef<AbortController | null>(null); | |
| useEffect(() => { | |
| let cancelled = false; | |
| const controller = new AbortController(); | |
| setProgress((current) => ({ ...current, label: 'Inspecting manifest and browser runtime' })); | |
| void Promise.all([ | |
| loadModelManifestV2(manifestUrl(), controller.signal), | |
| client.capabilities(), | |
| ]).then(([loadedManifest, loadedCapabilities]) => { | |
| if (cancelled) return; | |
| const initial = loadedManifest.models.find((model) => model.id === DEFAULT_MODEL) | |
| ?? loadedManifest.models[0]; | |
| if (!initial) throw new Error('The benchmark manifest contains no model tiers.'); | |
| setManifest(loadedManifest); | |
| setCapabilities(loadedCapabilities); | |
| setModelId(initial.id); | |
| setContextSize(initial.defaultContext); | |
| setProgress({ | |
| label: 'Ready · no model is loaded until Run benchmark is pressed', | |
| loadedBytes: 0, | |
| totalBytes: 0, | |
| }); | |
| }).catch((bootstrapError) => { | |
| if (cancelled || controller.signal.aborted) return; | |
| setBootstrapError(errorMessage(bootstrapError)); | |
| setProgress((current) => ({ ...current, label: 'Runtime inspection failed' })); | |
| }); | |
| return () => { | |
| cancelled = true; | |
| controller.abort(); | |
| }; | |
| }, [client]); | |
| const model = useMemo( | |
| () => manifest?.models.find((candidate) => candidate.id === modelId) ?? null, | |
| [manifest, modelId], | |
| ); | |
| const workload = BENCHMARK_WORKLOADS[workloadId]; | |
| const deviceGate = useMemo(() => { | |
| if (!model || !capabilities) return null; | |
| return evaluateModelGate(model, backend, capabilities.webgpu, { | |
| usageBytes: null, | |
| quotaBytes: null, | |
| persisted: capabilities.storage.persisted, | |
| }); | |
| }, [backend, capabilities, model]); | |
| const storageGate = useMemo(() => { | |
| if (!model || !capabilities) return null; | |
| return evaluateModelGate(model, backend, capabilities.webgpu, capabilities.storage); | |
| }, [backend, capabilities, model]); | |
| const contextPolicy = model | |
| ? evaluateModelContextPolicy(model, contextSize, { | |
| tuningScope: 'benchmark', | |
| requestedBackend: backend, | |
| flashMode, | |
| kvCacheType, | |
| }) | |
| : null; | |
| const contextLimit = contextPolicy?.limit ?? 4_096; | |
| const nBatch = parseOptionalInteger(nBatchInput); | |
| const nUbatch = parseOptionalInteger(nUbatchInput); | |
| const batchTuningError = nBatchInput.trim() !== '' && nBatch === null | |
| ? `nBatch must be an integer from 1 to ${MAX_GLUE_INT.toLocaleString()}.` | |
| : nUbatchInput.trim() !== '' && nUbatch === null | |
| ? `nUbatch must be an integer from 1 to ${MAX_GLUE_INT.toLocaleString()}.` | |
| : nBatch !== null && nUbatch !== null && nUbatch > nBatch | |
| ? 'nUbatch must not exceed nBatch.' | |
| : null; | |
| const runtimeTuningError = flashMode === 'off' && kvCacheType !== 'f16' | |
| ? 'Quantized KV requires Flash Attention auto.' | |
| : flashMode === 'auto' && backend !== 'webgpu' | |
| ? 'Flash Attention experiments require the explicit WebGPU backend.' | |
| : null; | |
| const wasmTuningError = wasmFlavor === 'jspi' && capabilities?.runtime.wasmFlavor === 'compat' | |
| ? 'JSPI is unavailable in this browser; use auto or compat.' | |
| : null; | |
| const workloadError = workload.requiredMaxTokens !== null && maxTokens !== workload.requiredMaxTokens | |
| ? `${workload.id} requires exactly ${workload.requiredMaxTokens.toLocaleString()} max tokens.` | |
| : null; | |
| const stateDriftReferenceError = workload.id === 'state-drift-1k-v1' && ( | |
| model?.id !== '27b' | |
| || backend !== 'webgpu' | |
| || contextSize !== 2_048 | |
| || nBatch !== 32 | |
| || nUbatch !== 16 | |
| ) | |
| ? 'state-drift-1k-v1 reference scoring requires 27B, explicit WebGPU, context 2,048, nBatch 32, and nUbatch 16.' | |
| : null; | |
| const configurationTuningError = batchTuningError | |
| ?? runtimeTuningError | |
| ?? wasmTuningError | |
| ?? workloadError | |
| ?? stateDriftReferenceError; | |
| const configurationValid = contextPolicy?.allowed === true | |
| && Number.isSafeInteger(maxTokens) | |
| && maxTokens >= 8 | |
| && maxTokens <= 1_024 | |
| && configurationTuningError === null; | |
| const canRun = Boolean(manifest && capabilities && model && deviceGate?.allowed && configurationValid); | |
| const clearPreviousRun = () => { | |
| setReport(null); | |
| setOutput(''); | |
| setRunError(null); | |
| if (manifest && capabilities) { | |
| setProgress({ | |
| label: 'Ready · settings changed; run again to produce matching evidence', | |
| loadedBytes: 0, | |
| totalBytes: 0, | |
| }); | |
| } | |
| }; | |
| const handleModelChange = (nextId: ModelTierId) => { | |
| if (running || !manifest) return; | |
| const next = manifest.models.find((candidate) => candidate.id === nextId); | |
| if (!next) return; | |
| setModelId(nextId); | |
| setContextSize(next.defaultContext); | |
| if (!next.cpuFallback && backend === 'wasm') setBackend('auto'); | |
| clearPreviousRun(); | |
| }; | |
| const runBenchmark = () => { | |
| if (!canRun || !manifest || !capabilities || !model || operationRef.current) return; | |
| const controller = new AbortController(); | |
| operationRef.current = controller; | |
| setRunning(true); | |
| setReport(null); | |
| setOutput(''); | |
| setRunError(null); | |
| const startedAt = new Date().toISOString(); | |
| const run = async () => { | |
| let storagePersistent = capabilities.storage.persisted; | |
| setProgress({ label: 'Requesting persistent browser storage', loadedBytes: 0, totalBytes: 0 }); | |
| try { | |
| const storage = await client.storagePersist(); | |
| storagePersistent = storage.persisted; | |
| setCapabilities((current) => current ? { ...current, storage } : current); | |
| } catch { | |
| // Persistence is a browser policy choice. The runtime storage gate remains authoritative. | |
| } | |
| throwIfAborted(controller.signal); | |
| await client.unload(); | |
| throwIfAborted(controller.signal); | |
| let coldCachedBytes: number | null = null; | |
| const loadParams = { | |
| manifest, | |
| modelId: model.id, | |
| backend, | |
| contextSize, | |
| benchmarkTuning: { | |
| ...(nBatch === null ? {} : { nBatch }), | |
| ...(nUbatch === null ? {} : { nUbatch }), | |
| flashMode, | |
| kvCacheType, | |
| wasmFlavor, | |
| }, | |
| }; | |
| const coldStarted = performance.now(); | |
| const coldLoad = await client.loadModel(loadParams, { | |
| signal: controller.signal, | |
| onProgress: (event) => { | |
| if (event.phase === 'download' && coldCachedBytes === null) { | |
| coldCachedBytes = event.loadedBytes; | |
| } | |
| const shard = event.shardIndex === null ? '' : ` · shard ${event.shardIndex + 1}/${event.shardCount}`; | |
| setProgress({ | |
| label: `Cold load · ${event.phase}${shard}`, | |
| loadedBytes: event.loadedBytes, | |
| totalBytes: event.totalBytes, | |
| }); | |
| }, | |
| }); | |
| const coldLoadMs = performance.now() - coldStarted; | |
| throwIfAborted(controller.signal); | |
| setProgress({ label: 'Unloading model before verified-cache reload', loadedBytes: 0, totalBytes: 0 }); | |
| await client.unload(); | |
| throwIfAborted(controller.signal); | |
| const warmStarted = performance.now(); | |
| const warmLoad = await client.loadModel(loadParams, { | |
| signal: controller.signal, | |
| onProgress: (event) => { | |
| const shard = event.shardIndex === null ? '' : ` · shard ${event.shardIndex + 1}/${event.shardCount}`; | |
| setProgress({ | |
| label: `Warm load · ${event.phase}${shard}`, | |
| loadedBytes: event.loadedBytes, | |
| totalBytes: event.totalBytes, | |
| }); | |
| }, | |
| }); | |
| const warmLoadMs = performance.now() - warmStarted; | |
| if (coldLoad.backend !== warmLoad.backend) { | |
| throw new Error(`Backend changed between cold and warm loads (${coldLoad.backend} → ${warmLoad.backend}).`); | |
| } | |
| if (coldLoad.tuning.observed.wasmFlavor !== warmLoad.tuning.observed.wasmFlavor) { | |
| throw new Error( | |
| `WASM flavor changed between cold and warm loads (${coldLoad.tuning.observed.wasmFlavor} → ${warmLoad.tuning.observed.wasmFlavor}).`, | |
| ); | |
| } | |
| throwIfAborted(controller.signal); | |
| let firstTokenAt: number | null = null; | |
| let streamedTokenEvents = 0; | |
| const generationStarted = performance.now(); | |
| const generationRequest = { | |
| messages: workload.messages.map((message) => ({ ...message })), | |
| maxTokens, | |
| temperature: 0, | |
| topK: 1, | |
| topP: 1, | |
| seed: 42, | |
| toolChoice: 'none', | |
| cachePrompt: false, | |
| returnTokenIds: true, | |
| } satisfies GenerateParams; | |
| setProgress({ label: `Generating fixed workload · ${workload.id}`, loadedBytes: 0, totalBytes: maxTokens }); | |
| const generationResult = await client.generate(generationRequest, { | |
| signal: controller.signal, | |
| onToken: (text, reasoningDelta) => { | |
| if (!text && !reasoningDelta) return; | |
| if (firstTokenAt === null) firstTokenAt = performance.now(); | |
| streamedTokenEvents += 1; | |
| setProgress({ | |
| label: `Generating fixed prompt · ${streamedTokenEvents} stream event${streamedTokenEvents === 1 ? '' : 's'}`, | |
| loadedBytes: streamedTokenEvents, | |
| totalBytes: maxTokens, | |
| }); | |
| }, | |
| }); | |
| const generationCompleted = performance.now(); | |
| throwIfAborted(controller.signal); | |
| let teacherForcedReferenceScore: TeacherForcedReferenceScoreEvidence | null = null; | |
| if (workload.id === 'state-drift-1k-v1') { | |
| setProgress({ | |
| label: 'Loading and validating pinned CPU reference', | |
| loadedBytes: 0, | |
| totalBytes: 1, | |
| }); | |
| const fixtureResponse = await fetch(stateDriftReferenceUrl(), { | |
| signal: controller.signal, | |
| cache: 'no-cache', | |
| }); | |
| if (!fixtureResponse.ok) { | |
| throw new Error( | |
| `CPU reference fixture request failed with HTTP ${fixtureResponse.status}.`, | |
| ); | |
| } | |
| const fixture = validateStateDriftReferenceFixture( | |
| await fixtureResponse.json() as unknown, | |
| { model, capabilities, loadResult: warmLoad }, | |
| ); | |
| throwIfAborted(controller.signal); | |
| setProgress({ | |
| label: 'Teacher-forcing the 1,024-token CPU reference', | |
| loadedBytes: 0, | |
| totalBytes: fixture.referenceTokenIds.length, | |
| }); | |
| const score = await client.scoreSequence({ | |
| promptTokenIds: fixture.promptTokenIds, | |
| referenceTokenIds: fixture.referenceTokenIds, | |
| topK: 5, | |
| }, { signal: controller.signal }); | |
| teacherForcedReferenceScore = { fixture, score }; | |
| throwIfAborted(controller.signal); | |
| } | |
| const backendReport = await client.backendReport(); | |
| throwIfAborted(controller.signal); | |
| const nextReport = buildBenchmarkReport({ | |
| startedAt, | |
| completedAt: new Date().toISOString(), | |
| manifest, | |
| model, | |
| capabilities, | |
| requestedBackend: backend, | |
| contextSize: warmLoad.context.size, | |
| workloadId, | |
| generationRequest, | |
| coldLoadMs, | |
| warmLoadMs, | |
| coldCachedBytes, | |
| storagePersistent, | |
| loadResult: warmLoad, | |
| generationResult, | |
| generationElapsedMs: generationCompleted - generationStarted, | |
| timeToFirstTokenMs: firstTokenAt === null ? null : firstTokenAt - generationStarted, | |
| streamedTokenEvents, | |
| backendReport, | |
| teacherForcedReferenceScore, | |
| }); | |
| setReport(nextReport); | |
| setOutput(generationResult.text || generationResult.reasoningText); | |
| setProgress({ | |
| label: 'Benchmark complete · JSON report is ready', | |
| loadedBytes: 1, | |
| totalBytes: 1, | |
| }); | |
| }; | |
| void run().catch((runError) => { | |
| if (isAbort(runError, controller.signal)) { | |
| setProgress({ label: 'Benchmark stopped by operator', loadedBytes: 0, totalBytes: 0 }); | |
| return; | |
| } | |
| setRunError(errorMessage(runError)); | |
| setProgress({ label: 'Benchmark failed safely · no report exported', loadedBytes: 0, totalBytes: 0 }); | |
| }).finally(() => { | |
| if (operationRef.current === controller) operationRef.current = null; | |
| setRunning(false); | |
| }); | |
| }; | |
| const progressPercent = progress.totalBytes > 0 | |
| ? Math.min(100, Math.max(0, progress.loadedBytes / progress.totalBytes * 100)) | |
| : 0; | |
| const fullGateStorageOnly = storageGate && !storageGate.allowed && deviceGate?.allowed; | |
| return ( | |
| <div className="bench-page station-frame"> | |
| <header className="bench-masthead"> | |
| <div className="brand-block" aria-label="Bonsai browser benchmark"> | |
| <span className="brand-mark" aria-hidden="true"><span /></span> | |
| <div> | |
| <p className="eyebrow">Repeatable local inference</p> | |
| <h1>Bonsai <i>browser benchmark</i></h1> | |
| </div> | |
| </div> | |
| <div className="bench-masthead-note"> | |
| <strong>Nothing runs on arrival</strong> | |
| <span>One click performs two loads, one fixed prompt, and no network call beyond model assets.</span> | |
| </div> | |
| <a className="bench-return" href={import.meta.env.BASE_URL}>Open chat <span aria-hidden="true">↗</span></a> | |
| </header> | |
| <div className="bench-layout"> | |
| <aside className="bench-controls"> | |
| <p className="eyebrow">Benchmark settings</p> | |
| <h2>Configure one run</h2> | |
| <p className="bench-control-intro">The fixed prompt and temperature stay locked so exported runs remain comparable.</p> | |
| <form onSubmit={(event) => { event.preventDefault(); runBenchmark(); }}> | |
| <label className="bench-field"> | |
| <span>Workload</span> | |
| <select | |
| value={workloadId} | |
| disabled={running} | |
| onChange={(event) => { | |
| const nextId = event.target.value as BenchmarkWorkloadId; | |
| const next = BENCHMARK_WORKLOADS[nextId]; | |
| setWorkloadId(nextId); | |
| setMaxTokens(next.requiredMaxTokens ?? 64); | |
| clearPreviousRun(); | |
| }} | |
| > | |
| {Object.values(BENCHMARK_WORKLOADS).map((candidate) => ( | |
| <option key={candidate.id} value={candidate.id}>{candidate.label}</option> | |
| ))} | |
| </select> | |
| <small>{workload.description}</small> | |
| </label> | |
| <label className="bench-field"> | |
| <span>Model tier</span> | |
| <select | |
| value={modelId} | |
| disabled={running || !manifest} | |
| onChange={(event) => handleModelChange(event.target.value as ModelTierId)} | |
| > | |
| {(manifest?.models ?? []).map((candidate) => ( | |
| <option key={candidate.id} value={candidate.id}>{candidate.displayName}</option> | |
| ))} | |
| </select> | |
| <small>{model ? `${formatBytes(model.downloadBytes)} verified weights` : 'Loading manifest…'}</small> | |
| </label> | |
| <label className="bench-field"> | |
| <span>Backend</span> | |
| <select | |
| value={backend} | |
| disabled={running} | |
| onChange={(event) => { | |
| setBackend(event.target.value as RequestedBackend); | |
| clearPreviousRun(); | |
| }} | |
| > | |
| <option value="auto">Auto · gated</option> | |
| <option value="webgpu">WebGPU · explicit</option> | |
| <option value="wasm" disabled={model?.cpuFallback === false}>CPU-WASM</option> | |
| </select> | |
| <small>{deviceGate?.selectedBackend ? `Preflight selects ${deviceGate.selectedBackend}` : 'No backend selected'}</small> | |
| </label> | |
| <div className="bench-field-pair"> | |
| <label className="bench-field"> | |
| <span>Context</span> | |
| <input | |
| type="number" | |
| min="256" | |
| max={contextLimit} | |
| step="256" | |
| value={contextSize} | |
| disabled={running} | |
| onChange={(event) => { | |
| setContextSize(Number(event.target.value)); | |
| clearPreviousRun(); | |
| }} | |
| /> | |
| <small>Max {contextLimit.toLocaleString()}</small> | |
| </label> | |
| <label className="bench-field"> | |
| <span>Max tokens</span> | |
| <input | |
| type="number" | |
| min="8" | |
| max="1024" | |
| step="8" | |
| value={maxTokens} | |
| disabled={running} | |
| onChange={(event) => { | |
| setMaxTokens(Number(event.target.value)); | |
| clearPreviousRun(); | |
| }} | |
| /> | |
| <small>8–1,024</small> | |
| </label> | |
| </div> | |
| <details className="bench-experimental"> | |
| <summary>Experimental runtime tuning</summary> | |
| <p>Blank batch fields use the pinned runtime defaults. Every option is exported as an experiment, not a release recommendation.</p> | |
| <div className="bench-field-pair"> | |
| <label className="bench-field"> | |
| <span>nBatch</span> | |
| <input | |
| type="number" | |
| min="1" | |
| max={MAX_GLUE_INT} | |
| step="1" | |
| placeholder="runtime default" | |
| value={nBatchInput} | |
| disabled={running} | |
| onChange={(event) => { | |
| setNBatchInput(event.target.value); | |
| clearPreviousRun(); | |
| }} | |
| /> | |
| </label> | |
| <label className="bench-field"> | |
| <span>nUbatch</span> | |
| <input | |
| type="number" | |
| min="1" | |
| max={MAX_GLUE_INT} | |
| step="1" | |
| placeholder="runtime default" | |
| value={nUbatchInput} | |
| disabled={running} | |
| onChange={(event) => { | |
| setNUbatchInput(event.target.value); | |
| clearPreviousRun(); | |
| }} | |
| /> | |
| </label> | |
| </div> | |
| <div className="bench-field-pair"> | |
| <label className="bench-field"> | |
| <span>Flash Attention request</span> | |
| <select | |
| value={flashMode} | |
| disabled={running} | |
| onChange={(event) => { | |
| setFlashMode(event.target.value as BenchmarkFlashMode); | |
| clearPreviousRun(); | |
| }} | |
| > | |
| <option value="off">Off · default</option> | |
| <option value="auto">Auto · observed on</option> | |
| </select> | |
| </label> | |
| <label className="bench-field"> | |
| <span>KV cache K / V</span> | |
| <select | |
| value={kvCacheType} | |
| disabled={running} | |
| onChange={(event) => { | |
| setKvCacheType(event.target.value as BenchmarkKvCacheType); | |
| clearPreviousRun(); | |
| }} | |
| > | |
| <option value="f16">f16 / f16 · default</option> | |
| <option value="q8_0">q8_0 / q8_0 · exp.</option> | |
| <option value="q4_0">q4_0 / q4_0 · exp.</option> | |
| </select> | |
| </label> | |
| </div> | |
| <label className="bench-field"> | |
| <span>WASM runtime</span> | |
| <select | |
| value={wasmFlavor} | |
| disabled={running} | |
| onChange={(event) => { | |
| setWasmFlavor(event.target.value as BenchmarkWasmFlavor); | |
| clearPreviousRun(); | |
| }} | |
| > | |
| <option value="auto">Auto · browser-selected</option> | |
| <option value="jspi">JSPI · explicit</option> | |
| <option value="compat">Compat / Asyncify · explicit</option> | |
| </select> | |
| <small>Current auto path: {capabilities?.runtime.wasmFlavor ?? 'inspecting'}</small> | |
| </label> | |
| <dl className="bench-locks"> | |
| <div><dt>Chat release</dt><dd>Flash off · KV f16/f16</dd></div> | |
| <div><dt>Evidence rule</dt><dd>native log must match or the run fails</dd></div> | |
| </dl> | |
| </details> | |
| {configurationTuningError && ( | |
| <p className="bench-inline-warning" role="alert">{configurationTuningError}</p> | |
| )} | |
| <div className={`bench-gate ${deviceGate?.allowed ? 'pass' : 'fail'}`}> | |
| <strong>{deviceGate?.allowed ? 'Device preflight passed' : 'Device preflight blocked'}</strong> | |
| <span> | |
| {deviceGate?.reasons[0] | |
| ?? (fullGateStorageOnly | |
| ? 'Storage cannot be proven before cache inspection; the worker repeats the authoritative gate.' | |
| : deviceGate?.warnings[0] ?? 'Waiting for capability inspection.')} | |
| </span> | |
| </div> | |
| <div className="bench-run-actions"> | |
| <button className="bench-run-button" type="submit" disabled={!canRun || running}> | |
| {running ? 'Run in progress' : 'Run benchmark'} | |
| </button> | |
| {running && ( | |
| <button | |
| className="bench-stop-button" | |
| type="button" | |
| onClick={() => { | |
| setProgress((current) => ({ ...current, label: 'Stopping benchmark…' })); | |
| operationRef.current?.abort(); | |
| }} | |
| > | |
| Stop | |
| </button> | |
| )} | |
| </div> | |
| </form> | |
| </aside> | |
| <main className="bench-workbench"> | |
| <section className="bench-hero"> | |
| <div> | |
| <p className="eyebrow ink">Phase 5 · operator evidence</p> | |
| <h2>Measure the whole <em>growth cycle.</em></h2> | |
| </div> | |
| <p>Cold load includes cache inspection and any missing download. Warm load repeats initialization after unload with verified shards retained. Generation uses one public, fixed prompt.</p> | |
| </section> | |
| <section className="bench-progress" aria-live="polite"> | |
| <div><strong>{progress.label}</strong><span>{progress.totalBytes > 1 ? `${Math.round(progressPercent)}%` : ''}</span></div> | |
| <span className="bench-progress-track"><i style={{ width: `${progressPercent}%` }} /></span> | |
| </section> | |
| {bootstrapError && <div className="bench-error" role="alert"><strong>Runtime inspection failed</strong><span>{bootstrapError}</span></div>} | |
| {runError && <div className="bench-error" role="alert"><strong>Run rejected</strong><span>{runError}</span></div>} | |
| <section className="bench-core-section" aria-label="Benchmark measurements"> | |
| <div className="bench-section-heading"> | |
| <div><p className="eyebrow ink">Core sample</p><h3>Four boundaries, one run</h3></div> | |
| {report && <button type="button" onClick={() => downloadReport(report)}>Export JSON</button>} | |
| </div> | |
| <div className="bench-core-sample"> | |
| <article> | |
| <span>Cold load</span> | |
| <strong>{formatDuration(report?.load.cold.durationMs)}</strong> | |
| <small>{report ? `${report.load.cold.cacheState} at start` : 'first boundary'}</small> | |
| </article> | |
| <article> | |
| <span>Warm load</span> | |
| <strong>{formatDuration(report?.load.warm.durationMs)}</strong> | |
| <small>verified cache retained</small> | |
| </article> | |
| <article> | |
| <span>TTFT</span> | |
| <strong>{formatDuration(report?.generation.timeToFirstTokenMs)}</strong> | |
| <small>request to first stream</small> | |
| </article> | |
| <article> | |
| <span>Decode</span> | |
| <strong>{formatRate(report?.generation.decodeTokensPerSecond)}</strong> | |
| <small>{report ? `${report.generation.completionTokens} completion tokens` : 'engine timing'}</small> | |
| </article> | |
| </div> | |
| </section> | |
| <section className="bench-detail-grid"> | |
| <article className="bench-data-card"> | |
| <p className="eyebrow ink">Throughput</p> | |
| <dl> | |
| <div><dt>Prompt</dt><dd>{formatRate(report?.generation.promptTokensPerSecond)}</dd></div> | |
| <div><dt>Decode</dt><dd>{formatRate(report?.generation.decodeTokensPerSecond)}</dd></div> | |
| <div><dt>Token count</dt><dd>{report?.generation.completionTokens ?? '—'}</dd></div> | |
| <div><dt>Finish</dt><dd>{report?.generation.finishReason ?? '—'}</dd></div> | |
| <div><dt>CPU-ref PPL</dt><dd>{report?.generation.teacherForcedReferenceScore?.score.summary.perplexity.toFixed(4) ?? '—'}</dd></div> | |
| <div><dt>CPU-ref mean NLL</dt><dd>{report?.generation.teacherForcedReferenceScore?.score.summary.meanNll.toFixed(6) ?? '—'}</dd></div> | |
| </dl> | |
| </article> | |
| <article className="bench-data-card"> | |
| <p className="eyebrow ink">Graph tripwire</p> | |
| <dl> | |
| <div><dt>Graph splits</dt><dd>{report?.execution.graphSplits ?? '—'}</dd></div> | |
| <div><dt>CPU ops</dt><dd>{report?.execution.opsOnCpu ?? '—'}</dd></div> | |
| <div><dt>GPU layers</dt><dd>{report?.execution.gpuLayers ? `${report.execution.gpuLayers.offloaded}/${report.execution.gpuLayers.total}` : '—'}</dd></div> | |
| <div><dt>Backend</dt><dd>{report?.execution.selectedBackend.toUpperCase() ?? '—'}</dd></div> | |
| </dl> | |
| </article> | |
| <article className="bench-data-card bench-runtime-card"> | |
| <p className="eyebrow ink">Runtime pins</p> | |
| <dl> | |
| <div><dt>Device</dt><dd>{report?.execution.device ?? capabilities?.webgpu.name ?? 'Inspecting…'}</dd></div> | |
| <div><dt>wllama</dt><dd title={report?.runtime.wllamaRevision}>{report?.runtime.wllamaRevision.slice(0, 12) ?? '—'}</dd></div> | |
| <div><dt>llama.cpp</dt><dd title={report?.runtime.llamaCppRevision}>{report?.runtime.llamaCppRevision.slice(0, 12) ?? '—'}</dd></div> | |
| <div><dt>Weights</dt><dd title={report?.runtime.manifestRevision ?? undefined}>{report?.runtime.manifestRevision?.slice(0, 12) ?? '—'}</dd></div> | |
| <div><dt>WASM</dt><dd>{report?.runtime.wasmFlavor.toUpperCase() ?? '—'}</dd></div> | |
| </dl> | |
| </article> | |
| <article className="bench-data-card bench-tuning-card"> | |
| <p className="eyebrow ink">Native tuning</p> | |
| <dl> | |
| <div><dt>Applied</dt><dd>{report ? (report.load.tuning.applied ? 'yes' : 'no') : '—'}</dd></div> | |
| <div><dt>Flash</dt><dd>{report ? `${report.load.tuning.requested.flashMode} → ${String(report.load.tuning.observed.flashAttention)}` : '—'}</dd></div> | |
| <div><dt>K/V</dt><dd>{report ? `${report.load.tuning.observed.cacheTypeK}/${report.load.tuning.observed.cacheTypeV}` : '—'}</dd></div> | |
| <div><dt>KV bytes</dt><dd>{report?.load.tuning.observed.kvBufferBytes === null || report?.load.tuning.observed.kvBufferBytes === undefined ? '—' : formatBytes(report.load.tuning.observed.kvBufferBytes)}</dd></div> | |
| </dl> | |
| </article> | |
| </section> | |
| <section className="bench-output"> | |
| <div> | |
| <p className="eyebrow ink">Fixed prompt</p> | |
| <h3>{workload.id}</h3> | |
| <p>{workload.messages.map((message) => message.content ?? '').join('\n')}</p> | |
| </div> | |
| <div> | |
| <p className="eyebrow ink">Generated output · visible text, or reasoning trace</p> | |
| <p data-testid="bench-generated-output">{output || 'Run the benchmark to inspect generated output.'}</p> | |
| </div> | |
| </section> | |
| </main> | |
| </div> | |
| </div> | |
| ); | |
| } | |