Spaces:
Running
Running
GitHub Actions commited on
Commit Β·
43cc902
1
Parent(s): cf2a028
sync from abhijitramesh/webgpu-bench@8b6281b77d
Browse files- js/run/bench-worker.js +35 -20
js/run/bench-worker.js
CHANGED
|
@@ -267,6 +267,27 @@ function describeError(err) {
|
|
| 267 |
return String(err);
|
| 268 |
}
|
| 269 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 270 |
self.onmessage = async (e) => {
|
| 271 |
const { type } = e.data || {};
|
| 272 |
if (type !== 'run') {
|
|
@@ -387,7 +408,7 @@ async function runOne({ params, opfsPath }) {
|
|
| 387 |
|
| 388 |
// βββ Init backend βββ
|
| 389 |
status('initializing', 'Initializing llama.cpp backends...');
|
| 390 |
-
const initResult = await
|
| 391 |
if (initResult !== 0) throw new Error(`bench_init failed: ${initResult}`);
|
| 392 |
log('Backends initialized');
|
| 393 |
|
|
@@ -396,12 +417,12 @@ async function runOne({ params, opfsPath }) {
|
|
| 396 |
// accidental mmap attempt surfaces as a clear error rather than a
|
| 397 |
// silent heap copy.
|
| 398 |
status('loading_model', `Loading model (ctx=${nCtx}, gpu_layers=${nGpuLayers}, mmap=0)...`);
|
| 399 |
-
const loadResult = await
|
|
|
|
| 400 |
'bench_load',
|
| 401 |
'number',
|
| 402 |
['string', 'number', 'number', 'number'],
|
| 403 |
['/model.gguf', nCtx, nGpuLayers, 0],
|
| 404 |
-
{ async: true },
|
| 405 |
);
|
| 406 |
if (loadResult !== 0) throw new Error(`bench_load failed: ${loadResult}`);
|
| 407 |
log('Model loaded');
|
|
@@ -412,7 +433,7 @@ async function runOne({ params, opfsPath }) {
|
|
| 412 |
// in try/catch β if the C side or a backend errors, the run can still
|
| 413 |
// produce perf numbers, just without memoryInfo on the record.
|
| 414 |
try {
|
| 415 |
-
const raw = await
|
| 416 |
result.memoryInfo = parseBenchResult('bench_memory_info', raw);
|
| 417 |
const dev = (result.memoryInfo.devices || [])
|
| 418 |
.map(d => `${d.name}(${d.type}) free=${(d.free / (1024 * 1024)).toFixed(0)}MB total=${(d.total / (1024 * 1024)).toFixed(0)}MB`)
|
|
@@ -431,24 +452,18 @@ async function runOne({ params, opfsPath }) {
|
|
| 431 |
try {
|
| 432 |
status('consistency', 'Running consistency check...', Date.now());
|
| 433 |
log(`bench_run("...", ${consistencyNPredict}) β consistency phase`);
|
| 434 |
-
const raw = await
|
| 435 |
-
'bench_run', 'string',
|
| 436 |
['string', 'number'],
|
| 437 |
-
[consistencyPrompt, consistencyNPredict]
|
| 438 |
-
{ async: true },
|
| 439 |
-
);
|
| 440 |
const r = parseBenchResult('bench_run', raw);
|
| 441 |
result.output = r.output || '';
|
| 442 |
result.consistency = { token_ids: r.token_ids || [] };
|
| 443 |
|
| 444 |
if (refTokenIds) {
|
| 445 |
log('bench_eval_tokens β forced-decode vs CPU baseline');
|
| 446 |
-
const evalRaw = await
|
| 447 |
-
'bench_eval_tokens', 'string',
|
| 448 |
['string', 'string'],
|
| 449 |
-
[consistencyPrompt, refTokenIds]
|
| 450 |
-
{ async: true },
|
| 451 |
-
);
|
| 452 |
const ev = parseBenchResult('bench_eval_tokens', evalRaw);
|
| 453 |
result.consistency = { ...result.consistency, ...ev };
|
| 454 |
if (ev.n_tokens < CONSISTENCY_MIN_TOKENS) {
|
|
@@ -482,7 +497,7 @@ async function runOne({ params, opfsPath }) {
|
|
| 482 |
// so reps 2..N at the same depth restore from snapshot instead of
|
| 483 |
// re-running the prefill (mirroring llama-bench's `cstate` reuse).
|
| 484 |
const setDepth = async (label) => {
|
| 485 |
-
const raw = await Module
|
| 486 |
const r = parseBenchResult(`bench_set_depth(${nDepth}) ${label}`, raw);
|
| 487 |
if (nDepth > 0) {
|
| 488 |
log(`bench_set_depth(${nDepth}) ${label}: ${r.cached ? 'restored snapshot' : 'prefilled'}`);
|
|
@@ -497,7 +512,7 @@ async function runOne({ params, opfsPath }) {
|
|
| 497 |
status('perf', `warmup pp${nPrompt}${depthSuffix}`, Date.now());
|
| 498 |
await setDepth('pp warmup');
|
| 499 |
log(`bench_pp(${nPrompt})${depthSuffix} β warmup`);
|
| 500 |
-
const raw = await Module
|
| 501 |
parseBenchResult('bench_pp warmup', raw);
|
| 502 |
}
|
| 503 |
const samples_ns = [];
|
|
@@ -505,7 +520,7 @@ async function runOne({ params, opfsPath }) {
|
|
| 505 |
status('perf', `pp${nPrompt}${depthSuffix} ${i + 1}/${nReps}`, Date.now());
|
| 506 |
await setDepth(`pp rep ${i + 1}/${nReps}`);
|
| 507 |
const t0 = performance.now();
|
| 508 |
-
const raw = await Module
|
| 509 |
const t_ns = (performance.now() - t0) * 1e6;
|
| 510 |
parseBenchResult('bench_pp', raw);
|
| 511 |
samples_ns.push(t_ns);
|
|
@@ -528,7 +543,7 @@ async function runOne({ params, opfsPath }) {
|
|
| 528 |
status('perf', `warmup tg${nGen}${depthSuffix}`, Date.now());
|
| 529 |
await setDepth('tg warmup');
|
| 530 |
log(`bench_tg(${nGen})${depthSuffix} β warmup`);
|
| 531 |
-
const raw = await Module
|
| 532 |
parseBenchResult('bench_tg warmup', raw);
|
| 533 |
}
|
| 534 |
const samples_ns = [];
|
|
@@ -536,7 +551,7 @@ async function runOne({ params, opfsPath }) {
|
|
| 536 |
status('perf', `tg${nGen}${depthSuffix} ${i + 1}/${nReps}`, Date.now());
|
| 537 |
await setDepth(`tg rep ${i + 1}/${nReps}`);
|
| 538 |
const t0 = performance.now();
|
| 539 |
-
const raw = await Module
|
| 540 |
const t_ns = (performance.now() - t0) * 1e6;
|
| 541 |
parseBenchResult('bench_tg', raw);
|
| 542 |
samples_ns.push(t_ns);
|
|
@@ -560,7 +575,7 @@ async function runOne({ params, opfsPath }) {
|
|
| 560 |
}
|
| 561 |
}
|
| 562 |
|
| 563 |
-
await
|
| 564 |
|
| 565 |
// Close the sync handle so OPFS can release its lock on the file (and
|
| 566 |
// so a subsequent run can open a fresh handle without colliding).
|
|
|
|
| 267 |
return String(err);
|
| 268 |
}
|
| 269 |
|
| 270 |
+
function formatPhaseError(phase, err) {
|
| 271 |
+
const detail = describeError(err);
|
| 272 |
+
return detail ? `${phase} threw WASM exception (${detail})` : `${phase} threw WASM exception`;
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
async function ccallPhase(Module, phase, returnType, argTypes, args) {
|
| 276 |
+
try {
|
| 277 |
+
return await Module.ccall(phase, returnType, argTypes, args, { async: true });
|
| 278 |
+
} catch (err) {
|
| 279 |
+
throw new Error(formatPhaseError(phase, err));
|
| 280 |
+
}
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
async function ccallPhaseLabel(Module, phaseLabel, exportName, returnType, argTypes, args) {
|
| 284 |
+
try {
|
| 285 |
+
return await Module.ccall(exportName, returnType, argTypes, args, { async: true });
|
| 286 |
+
} catch (err) {
|
| 287 |
+
throw new Error(formatPhaseError(phaseLabel, err));
|
| 288 |
+
}
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
self.onmessage = async (e) => {
|
| 292 |
const { type } = e.data || {};
|
| 293 |
if (type !== 'run') {
|
|
|
|
| 408 |
|
| 409 |
// βββ Init backend βββ
|
| 410 |
status('initializing', 'Initializing llama.cpp backends...');
|
| 411 |
+
const initResult = await ccallPhase(Module, 'bench_init', 'number', [], []);
|
| 412 |
if (initResult !== 0) throw new Error(`bench_init failed: ${initResult}`);
|
| 413 |
log('Backends initialized');
|
| 414 |
|
|
|
|
| 417 |
// accidental mmap attempt surfaces as a clear error rather than a
|
| 418 |
// silent heap copy.
|
| 419 |
status('loading_model', `Loading model (ctx=${nCtx}, gpu_layers=${nGpuLayers}, mmap=0)...`);
|
| 420 |
+
const loadResult = await ccallPhase(
|
| 421 |
+
Module,
|
| 422 |
'bench_load',
|
| 423 |
'number',
|
| 424 |
['string', 'number', 'number', 'number'],
|
| 425 |
['/model.gguf', nCtx, nGpuLayers, 0],
|
|
|
|
| 426 |
);
|
| 427 |
if (loadResult !== 0) throw new Error(`bench_load failed: ${loadResult}`);
|
| 428 |
log('Model loaded');
|
|
|
|
| 433 |
// in try/catch β if the C side or a backend errors, the run can still
|
| 434 |
// produce perf numbers, just without memoryInfo on the record.
|
| 435 |
try {
|
| 436 |
+
const raw = await ccallPhase(Module, 'bench_memory_info', 'string', [], []);
|
| 437 |
result.memoryInfo = parseBenchResult('bench_memory_info', raw);
|
| 438 |
const dev = (result.memoryInfo.devices || [])
|
| 439 |
.map(d => `${d.name}(${d.type}) free=${(d.free / (1024 * 1024)).toFixed(0)}MB total=${(d.total / (1024 * 1024)).toFixed(0)}MB`)
|
|
|
|
| 452 |
try {
|
| 453 |
status('consistency', 'Running consistency check...', Date.now());
|
| 454 |
log(`bench_run("...", ${consistencyNPredict}) β consistency phase`);
|
| 455 |
+
const raw = await ccallPhase(Module, 'bench_run', 'string',
|
|
|
|
| 456 |
['string', 'number'],
|
| 457 |
+
[consistencyPrompt, consistencyNPredict]);
|
|
|
|
|
|
|
| 458 |
const r = parseBenchResult('bench_run', raw);
|
| 459 |
result.output = r.output || '';
|
| 460 |
result.consistency = { token_ids: r.token_ids || [] };
|
| 461 |
|
| 462 |
if (refTokenIds) {
|
| 463 |
log('bench_eval_tokens β forced-decode vs CPU baseline');
|
| 464 |
+
const evalRaw = await ccallPhase(Module, 'bench_eval_tokens', 'string',
|
|
|
|
| 465 |
['string', 'string'],
|
| 466 |
+
[consistencyPrompt, refTokenIds]);
|
|
|
|
|
|
|
| 467 |
const ev = parseBenchResult('bench_eval_tokens', evalRaw);
|
| 468 |
result.consistency = { ...result.consistency, ...ev };
|
| 469 |
if (ev.n_tokens < CONSISTENCY_MIN_TOKENS) {
|
|
|
|
| 497 |
// so reps 2..N at the same depth restore from snapshot instead of
|
| 498 |
// re-running the prefill (mirroring llama-bench's `cstate` reuse).
|
| 499 |
const setDepth = async (label) => {
|
| 500 |
+
const raw = await ccallPhaseLabel(Module, `bench_set_depth(${nDepth}) ${label}`, 'bench_set_depth', 'string', ['number'], [nDepth]);
|
| 501 |
const r = parseBenchResult(`bench_set_depth(${nDepth}) ${label}`, raw);
|
| 502 |
if (nDepth > 0) {
|
| 503 |
log(`bench_set_depth(${nDepth}) ${label}: ${r.cached ? 'restored snapshot' : 'prefilled'}`);
|
|
|
|
| 512 |
status('perf', `warmup pp${nPrompt}${depthSuffix}`, Date.now());
|
| 513 |
await setDepth('pp warmup');
|
| 514 |
log(`bench_pp(${nPrompt})${depthSuffix} β warmup`);
|
| 515 |
+
const raw = await ccallPhaseLabel(Module, `bench_pp warmup (${nPrompt}${depthSuffix})`, 'bench_pp', 'string', ['number'], [nPrompt]);
|
| 516 |
parseBenchResult('bench_pp warmup', raw);
|
| 517 |
}
|
| 518 |
const samples_ns = [];
|
|
|
|
| 520 |
status('perf', `pp${nPrompt}${depthSuffix} ${i + 1}/${nReps}`, Date.now());
|
| 521 |
await setDepth(`pp rep ${i + 1}/${nReps}`);
|
| 522 |
const t0 = performance.now();
|
| 523 |
+
const raw = await ccallPhaseLabel(Module, `bench_pp rep ${i + 1}/${nReps} (${nPrompt}${depthSuffix})`, 'bench_pp', 'string', ['number'], [nPrompt]);
|
| 524 |
const t_ns = (performance.now() - t0) * 1e6;
|
| 525 |
parseBenchResult('bench_pp', raw);
|
| 526 |
samples_ns.push(t_ns);
|
|
|
|
| 543 |
status('perf', `warmup tg${nGen}${depthSuffix}`, Date.now());
|
| 544 |
await setDepth('tg warmup');
|
| 545 |
log(`bench_tg(${nGen})${depthSuffix} β warmup`);
|
| 546 |
+
const raw = await ccallPhaseLabel(Module, `bench_tg warmup (${nGen}${depthSuffix})`, 'bench_tg', 'string', ['number'], [nGen]);
|
| 547 |
parseBenchResult('bench_tg warmup', raw);
|
| 548 |
}
|
| 549 |
const samples_ns = [];
|
|
|
|
| 551 |
status('perf', `tg${nGen}${depthSuffix} ${i + 1}/${nReps}`, Date.now());
|
| 552 |
await setDepth(`tg rep ${i + 1}/${nReps}`);
|
| 553 |
const t0 = performance.now();
|
| 554 |
+
const raw = await ccallPhaseLabel(Module, `bench_tg rep ${i + 1}/${nReps} (${nGen}${depthSuffix})`, 'bench_tg', 'string', ['number'], [nGen]);
|
| 555 |
const t_ns = (performance.now() - t0) * 1e6;
|
| 556 |
parseBenchResult('bench_tg', raw);
|
| 557 |
samples_ns.push(t_ns);
|
|
|
|
| 575 |
}
|
| 576 |
}
|
| 577 |
|
| 578 |
+
await ccallPhase(Module, 'bench_exit', null, [], []);
|
| 579 |
|
| 580 |
// Close the sync handle so OPFS can release its lock on the file (and
|
| 581 |
// so a subsequent run can open a fresh handle without colliding).
|