Bonsai-Chat-WebGPU / src /bench /PromptMatrixApp.tsx
Valeriy Selitskiy
Release v1.2.2 interactive chat workspace
21ad36a
Raw
History Blame Contribute Delete
10 kB
import { useEffect, useState } from 'react';
import {
BrowserEngineClient,
EngineClientError,
loadModelManifestV2,
type EngineCapabilities,
type BenchmarkWasmFlavor,
type ModelManifestV2,
type RuntimeBackend,
} from '../engine';
import {
parseReleasePromptMatrix,
RELEASE_PROMPT_MATRIX_MAX_TOKENS,
RELEASE_PROMPT_MATRIX_PATH,
type ReleasePromptCase,
} from './prompt-matrix';
const MANIFEST_PATH = 'manifest/models.json';
const MATRIX_MODEL_ID = '1_7b';
const MATRIX_CONTEXT_SIZE = 2_048;
let matrixClient: BrowserEngineClient | null = null;
function client(): BrowserEngineClient {
matrixClient ??= new BrowserEngineClient();
return matrixClient;
}
function assetUrl(path: string): string {
return new URL(`${import.meta.env.BASE_URL}${path}`, document.baseURI).href;
}
function errorMessage(error: unknown): string {
if (error instanceof EngineClientError) {
return `${error.code}: ${error.message}${error.details === undefined ? '' : `\n${JSON.stringify(error.details)}`}`;
}
return error instanceof Error ? error.message : String(error);
}
async function sha256Text(value: string): Promise<string> {
const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(value));
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, '0')).join('');
}
interface BootstrapState {
manifest: ModelManifestV2;
capabilities: EngineCapabilities;
cases: ReleasePromptCase[];
fixtureSha256: string;
}
export function PromptMatrixApp() {
const [bootstrap, setBootstrap] = useState<BootstrapState | null>(null);
const [backend, setBackend] = useState<RuntimeBackend>('wasm');
const [wasmFlavor, setWasmFlavor] = useState<BenchmarkWasmFlavor>('auto');
const [running, setRunning] = useState(false);
const [progress, setProgress] = useState('Inspecting the pinned fixture and runtime');
const [error, setError] = useState<string | null>(null);
const [report, setReport] = useState<unknown>(null);
useEffect(() => {
const controller = new AbortController();
const engine = client();
void Promise.all([
loadModelManifestV2(assetUrl(MANIFEST_PATH), controller.signal),
engine.capabilities(),
fetch(assetUrl(RELEASE_PROMPT_MATRIX_PATH), { signal: controller.signal }).then(async (response) => {
if (!response.ok) throw new Error(`Prompt fixture request failed with HTTP ${response.status}.`);
const text = await response.text();
return {
cases: parseReleasePromptMatrix(JSON.parse(text)),
fixtureSha256: await sha256Text(text),
};
}),
]).then(([manifest, capabilities, fixture]) => {
setBootstrap({ manifest, capabilities, ...fixture });
setProgress('Ready · model remains unloaded until the matrix is started');
}).catch((bootstrapError) => {
if (controller.signal.aborted) return;
setError(errorMessage(bootstrapError));
setProgress('Prompt matrix bootstrap failed');
});
return () => controller.abort();
}, []);
const runMatrix = () => {
if (!bootstrap || running) return;
const engine = client();
setRunning(true);
setError(null);
setReport(null);
const startedAt = new Date().toISOString();
void (async () => {
await engine.unload();
const loadStarted = performance.now();
const load = await engine.loadModel({
manifest: bootstrap.manifest,
modelId: MATRIX_MODEL_ID,
backend,
contextSize: MATRIX_CONTEXT_SIZE,
...(wasmFlavor === 'auto' ? {} : {
benchmarkTuning: {
flashMode: 'off' as const,
kvCacheType: 'f16' as const,
wasmFlavor,
},
}),
}, {
onProgress: (event) => {
const shard = event.shardIndex === null ? '' : ` · shard ${event.shardIndex + 1}/${event.shardCount}`;
setProgress(`Loading 1.7B ${backend.toUpperCase()} · ${event.phase}${shard}`);
},
});
const loadMs = performance.now() - loadStarted;
const results = [];
for (const [index, fixture] of bootstrap.cases.entries()) {
setProgress(`Generating ${fixture.id} · ${index + 1}/${bootstrap.cases.length}`);
let generated;
try {
generated = await engine.generate({
messages: fixture.messages,
maxTokens: RELEASE_PROMPT_MATRIX_MAX_TOKENS,
temperature: 0,
topP: 1,
topK: 1,
seed: 42,
...(fixture.tools.length > 0 ? {
tools: fixture.tools,
toolChoice: 'auto' as const,
} : { toolChoice: 'none' as const }),
cachePrompt: false,
returnTokenIds: true,
});
} catch (generationError) {
throw new Error(`${fixture.id}: ${errorMessage(generationError)}`);
}
if (!generated.tokenIds || !generated.tokenTrace
|| generated.tokenIds.length === 0
|| generated.tokenIds.length !== generated.tokenTrace.length
|| generated.tokenTrace.some((entry, tokenIndex) => (
entry.selected.id !== generated.tokenIds?.[tokenIndex]
|| entry.topCandidates.length !== 5
|| entry.topCandidates[0]?.id !== generated.tokenIds?.[tokenIndex]
))) {
throw new Error(`${fixture.id}: runtime returned an invalid greedy top-five token trace.`);
}
results.push({
id: fixture.id,
messages: fixture.messages,
tools: fixture.tools,
generatedTokenIds: generated.tokenIds,
tokenTrace: generated.tokenTrace,
tokenTraceAccounting: generated.tokenTraceAccounting ?? null,
text: generated.text,
reasoningText: generated.reasoningText,
toolCalls: generated.toolCalls,
finishReason: generated.finishReason,
usage: generated.usage,
timings: generated.timings,
});
}
const backendReport = await engine.backendReport();
const evidence = {
schemaVersion: 1,
kind: 'bonsai-browser-prompt-matrix',
startedAt,
completedAt: new Date().toISOString(),
endpoint: window.location.origin,
policy: {
modelTier: MATRIX_MODEL_ID,
contextSize: MATRIX_CONTEXT_SIZE,
backend,
wasmFlavor,
maxTokens: RELEASE_PROMPT_MATRIX_MAX_TOKENS,
temperature: 0,
topK: 1,
topP: 1,
seed: 42,
cachePrompt: false,
topLogprobs: 5,
},
fixture: {
path: RELEASE_PROMPT_MATRIX_PATH,
sha256: bootstrap.fixtureSha256,
cases: bootstrap.cases.length,
},
runtime: bootstrap.capabilities.runtime,
browser: bootstrap.capabilities.browser,
crossOriginIsolated: bootstrap.capabilities.crossOriginIsolated,
sharedArrayBuffer: bootstrap.capabilities.sharedArrayBuffer,
load: {
durationMs: loadMs,
selectedBackend: load.backend,
context: load.context,
tuning: load.tuning,
backendReport: load.backendReport,
},
backendReport,
cases: results,
checks: {
atLeastSixCases: results.length >= 6,
everyCaseGenerated: results.every((entry) => entry.generatedTokenIds.length > 0),
everyCaseHasGreedyTopFiveTrace: results.every((entry) => (
entry.generatedTokenIds.length === entry.tokenTrace.length
&& entry.tokenTrace.every((trace, tokenIndex) => (
trace.selected.id === entry.generatedTokenIds[tokenIndex]
&& trace.topCandidates.length === 5
&& trace.topCandidates[0]?.id === entry.generatedTokenIds[tokenIndex]
))
)),
},
};
if (Object.values(evidence.checks).some((value) => value !== true)) {
throw new Error(`Prompt matrix checks failed: ${JSON.stringify(evidence.checks)}`);
}
setReport(evidence);
setProgress('Prompt matrix complete · JSON evidence is ready');
})().catch((runError) => {
setError(errorMessage(runError));
setProgress('Prompt matrix failed');
}).finally(() => setRunning(false));
};
return (
<main className="matrix-page">
<header>
<div>
<p className="eyebrow">Release diagnostics</p>
<h1>Bonsai prompt matrix</h1>
</div>
<a href="./">Return to chat</a>
</header>
<section className="matrix-controls">
<label>
Backend
<select
aria-label="Matrix backend"
value={backend}
disabled={running}
onChange={(event) => setBackend(event.target.value as RuntimeBackend)}
>
<option value="wasm">CPU · WASM</option>
<option value="webgpu">WebGPU</option>
</select>
</label>
<label>
WASM runtime
<select
aria-label="Matrix WASM runtime"
value={wasmFlavor}
disabled={running}
onChange={(event) => setWasmFlavor(event.target.value as BenchmarkWasmFlavor)}
>
<option value="auto">Auto · browser-selected</option>
<option value="jspi">JSPI · explicit</option>
<option value="compat">Compat / Asyncify · explicit</option>
</select>
</label>
<button type="button" disabled={!bootstrap || running} onClick={runMatrix}>
{running ? 'Running matrix…' : 'Run release prompt matrix'}
</button>
</section>
<p className="matrix-progress" aria-live="polite">{progress}</p>
{error ? <pre className="matrix-error" role="alert">{error}</pre> : null}
{report ? (
<pre className="matrix-report" data-testid="prompt-matrix-report">
{JSON.stringify(report, null, 2)}
</pre>
) : null}
</main>
);
}