@localmode/bench 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -5,16 +5,20 @@
5
5
  * published statistics can be recomputed and audited server-side.
6
6
  */
7
7
  /** Protocol identifier embedded in every result. Bump only with a spec change. */
8
- declare const BENCH_PROTOCOL_VERSION = "localmode-bench/2";
8
+ declare const BENCH_PROTOCOL_VERSION = "localmode-bench/3";
9
9
  /** Result JSON schema version (independent of the protocol semantics version). */
10
10
  declare const BENCH_SCHEMA_VERSION = 2;
11
11
  /** Benchmark suite presets. `custom` = user-picked cells. */
12
12
  type BenchSuiteId = 'quick' | 'standard' | 'thorough' | 'custom';
13
13
  /**
14
14
  * A benchmark runtime lane. Transformers.js is split into two lanes because
15
- * WebGPU vs WASM is the paper's core comparison axis.
15
+ * WebGPU vs WASM is the core comparison axis.
16
16
  */
17
- type BenchRuntimeId = 'transformers-webgpu' | 'transformers-wasm' | 'webllm' | 'wllama' | 'litert' | 'chrome-ai' | 'mediapipe';
17
+ type BenchRuntimeId = 'transformers-webgpu' | 'transformers-wasm' | 'webllm'
18
+ /** llama.cpp WASM on the CPU (`n_gpu_layers: 0`); before protocol v3 this lane silently ran on WebGPU where available. */
19
+ | 'wllama'
20
+ /** llama.cpp WASM with every layer offloaded to WebGPU. */
21
+ | 'wllama-webgpu' | 'litert' | 'chrome-ai' | 'mediapipe';
18
22
  /** Workload kinds. Quality lanes are separate from timed performance lanes. */
19
23
  type BenchWorkloadKind = 'llm-generate' | 'embed-single' | 'embed-batch' | 'quality-mmlu' | 'quality-sts';
20
24
  /** A fixed LLM generation workload (public prompt, deterministic settings). */
@@ -169,14 +173,34 @@ interface BenchCellResult {
169
173
  workloadKind: BenchWorkloadKind;
170
174
  /** Backend actually used (probed, never the requested one). */
171
175
  resolvedBackend: string;
176
+ /**
177
+ * Runtime configuration the adapter reports once the model is loaded
178
+ * (thread count, GPU layers requested and llama.cpp's offload report,
179
+ * dtype, ...): the equal-care record for the lane, per cell.
180
+ */
181
+ runtimeConfig?: Record<string, string | number | boolean>;
172
182
  load: LoadRecord | null;
173
183
  /** Untimed warmup duration (ms), when a warmup ran. */
174
184
  warmupMs?: number;
175
185
  iterations: LLMIteration[] | EmbedIteration[];
186
+ /**
187
+ * Timed iterations the tab was hidden during, kept with their gates for
188
+ * auditability and repeated once the tab was visible again; never scored.
189
+ */
190
+ discardedIterations?: Array<LLMIteration | EmbedIteration>;
176
191
  memory?: MemorySample;
177
192
  quality?: QualityResult;
178
193
  status: BenchCellStatus;
179
194
  invalidReasons?: string[];
195
+ /**
196
+ * Failed attempts that preceded the recorded outcome (a watchdog timeout, a
197
+ * provider error), oldest first. The runner retries a cell up to the
198
+ * policy's `maxAttempts`; nothing is retried silently.
199
+ */
200
+ attempts?: Array<{
201
+ error: NonNullable<BenchCellResult['error']>;
202
+ at: number;
203
+ }>;
180
204
  /** Error that ended the cell; `cause` carries the wrapped provider error's message when present. */
181
205
  error?: {
182
206
  name: string;
@@ -192,7 +216,7 @@ interface BenchCellResult {
192
216
  /** Trace events global to the suite run (validity accounting). */
193
217
  interface TraceEvent {
194
218
  t: number;
195
- type: 'suite-start' | 'suite-end' | 'visibility-hidden' | 'visibility-visible' | 'wakelock-acquired' | 'wakelock-released' | 'pressure-change' | 'gpu-device-lost' | 'cooldown-start' | 'cooldown-end' | 'abort';
219
+ type: 'suite-start' | 'suite-end' | 'visibility-hidden' | 'visibility-visible' | 'wakelock-acquired' | 'wakelock-released' | 'pressure-change' | 'gpu-device-lost' | 'cooldown-start' | 'cooldown-end' | 'cell-timeout' | 'cell-retry' | 'iteration-redo' | 'abort';
196
220
  detail?: string;
197
221
  }
198
222
  /** Browser identification with its provenance. */
@@ -319,7 +343,7 @@ interface WasmFeatureSupport {
319
343
  maxMemoryPages?: number;
320
344
  }
321
345
  /**
322
- * Availability of the browser APIs the runtimes and the paper care about.
346
+ * Availability of the browser APIs the runtimes and the analysis care about.
323
347
  * Each entry is a plain presence check (the feature exists on this page),
324
348
  * not a functional test. Chrome Built-in AI is reported by its
325
349
  * `availability()` string where the API exists.
@@ -405,7 +429,7 @@ interface EnvironmentCapture {
405
429
  browser: BrowserInfo;
406
430
  os: OSInfo;
407
431
  hardware: {
408
- /** navigator.hardwareConcurrency — clamped/randomized on Gecko/WebKit. */
432
+ /** navigator.hardwareConcurrency — WebKit clamps it (8 on macOS, 4 on iOS); Chromium and Firefox report the real count. */
409
433
  cores: number | null;
410
434
  coresClamped: boolean;
411
435
  /** navigator.deviceMemory (GB) — Chromium-only, capped at 8. */
@@ -612,11 +636,17 @@ declare const WORKLOADS_BY_ID: ReadonlyMap<string, LLMWorkloadSpec | EmbedWorklo
612
636
  /**
613
637
  * Fixed runtime execution order (part of the versioned protocol). This makes
614
638
  * the order deterministic so runtime interleaving is not a confounder across
615
- * runs; it is a reproducibility measure, not a correctness fix. (The
616
- * Transformers.js ORT-web lanes fail during session creation regardless of
617
- * where they run in the order - a separate open runtime/adapter issue.) The
618
- * order runs the WASM-arena runtimes first and the multi-GB-heap runtimes last.
619
- * Within a runtime, catalog order is preserved.
639
+ * runs; it is a reproducibility measure, not a correctness fix. The order runs
640
+ * the WASM-arena runtimes first and the multi-GB-heap runtimes last; within a
641
+ * runtime, catalog order is preserved.
642
+ *
643
+ * The Transformers.js WASM lane runs before its WebGPU lane: Transformers.js
644
+ * serializes every ONNX session creation on one promise chain and never
645
+ * catches a rejection on it, so the first session that fails to create (a
646
+ * WebGPU execution provider the browser cannot initialize, an allocation
647
+ * failure under memory pressure) fails every later Transformers.js session in
648
+ * the page with the same error. Running the WASM lane first keeps a WebGPU
649
+ * failure from taking the CPU measurement with it.
620
650
  */
621
651
  declare const RUNTIME_EXECUTION_ORDER: readonly BenchRuntimeId[];
622
652
  /**
@@ -644,6 +674,39 @@ interface RunPolicy {
644
674
  measureWarmReload: boolean;
645
675
  /** CV above this fraction marks a summary metric as high-variance. */
646
676
  highVarianceCv: number;
677
+ /**
678
+ * Watchdog: a load that reports no progress for this long is aborted as a
679
+ * timeout (stall-based, so a slow link keeps downloading as long as bytes
680
+ * arrive).
681
+ */
682
+ loadStallMs: number;
683
+ /** Watchdog: absolute cap on one load attempt. */
684
+ loadTimeoutMs: number;
685
+ /**
686
+ * Watchdog: a generation (warmup or timed iteration) whose stream delivers
687
+ * nothing for this long is aborted as a timeout. A runtime surface that
688
+ * flushes every chunk in a terminal burst (LiteRT-LM) stays silent for the
689
+ * whole request, so this is well above a full request.
690
+ */
691
+ chunkStallMs: number;
692
+ /** Watchdog: absolute cap on one generation or embedding call. */
693
+ iterationTimeoutMs: number;
694
+ /** Watchdog: absolute cap on one quality-lane cell (many generations). */
695
+ qualityTimeoutMs: number;
696
+ /**
697
+ * Attempts per cell (and per model load) before the cell is recorded as an
698
+ * error and the run moves on. Every failed attempt stays on the cell in
699
+ * `attempts`; retries are never silent.
700
+ */
701
+ maxAttempts: number;
702
+ /**
703
+ * How long the runner waits for a hidden tab to become visible again before
704
+ * a timed iteration starts, or before it repeats an iteration the tab hid
705
+ * during. An iteration measured while hidden is kept on the cell in
706
+ * `discardedIterations` and repeated (up to `maxAttempts` times per
707
+ * iteration); a tab that stays hidden past this wait leaves the cell invalid.
708
+ */
709
+ visibilityWaitMs: number;
647
710
  }
648
711
  /** Policies per suite preset. */
649
712
  declare const RUN_POLICIES: Record<'quick' | 'standard' | 'thorough', RunPolicy>;
@@ -722,12 +785,15 @@ interface LoadedLLM {
722
785
  model: BenchLanguageModel;
723
786
  /** Backend actually in use — 'webgpu' | 'wasm' | 'gpu' | 'cpu' | 'chrome-builtin'. */
724
787
  resolvedBackend: string;
788
+ /** Post-load runtime configuration worth recording on every cell (threads, GPU layers, dtype, ...). */
789
+ runtimeConfig?: Record<string, string | number | boolean>;
725
790
  dispose(): Promise<void>;
726
791
  }
727
792
  /** A loaded embedding-model handle. */
728
793
  interface LoadedEmbedder {
729
794
  model: BenchEmbeddingModel;
730
795
  resolvedBackend: string;
796
+ runtimeConfig?: Record<string, string | number | boolean>;
731
797
  dispose(): Promise<void>;
732
798
  }
733
799
  /** Adapter for an LLM runtime lane. */
@@ -761,6 +827,37 @@ interface EmbeddingRuntimeAdapter {
761
827
  /** Usage fidelity per runtime — how trustworthy provider token counts are. */
762
828
  declare const USAGE_FIDELITY: Record<BenchRuntimeId, ProviderUsage['fidelity']>;
763
829
 
830
+ /**
831
+ * Suite-level trace recorder: validity-relevant events (tab visibility, wake
832
+ * lock, compute pressure, GPU device loss, aborts) with wall-clock timestamps.
833
+ * Iterations that overlap a hidden period are invalidated, never retried
834
+ * silently — the trace is part of the submitted, auditable record.
835
+ */
836
+
837
+ /** Records trace events and tracks the current validity-gate state. */
838
+ declare class TraceRecorder {
839
+ private readonly events;
840
+ private disposers;
841
+ private hidden;
842
+ private lastPressureState;
843
+ private wakeLockSentinel;
844
+ /** Append an event at the current timestamp. */
845
+ record(type: TraceEvent['type'], detail?: string): void;
846
+ /** True while the page is hidden (timed regions overlapping this are invalid). */
847
+ get isHidden(): boolean;
848
+ /** Last observed compute-pressure state ('nominal'|'fair'|'serious'|'critical'). */
849
+ get pressureState(): string | undefined;
850
+ /** All recorded events (live reference; snapshot with `[...events]`). */
851
+ get all(): readonly TraceEvent[];
852
+ /**
853
+ * Attach browser listeners (visibility, compute pressure) and request a
854
+ * screen wake lock. Safe to call outside a browser (records nothing).
855
+ */
856
+ attach(): Promise<void>;
857
+ /** Release listeners and the wake lock. */
858
+ dispose(): Promise<void>;
859
+ }
860
+
764
861
  /**
765
862
  * The suite runner: executes (runtime x model x workload) cells under the
766
863
  * versioned run policy — cache probe, load, untimed warmup, N timed runs with
@@ -779,6 +876,20 @@ interface PlannedCell {
779
876
  */
780
877
  skipReason?: string;
781
878
  }
879
+ /** A live-activity report: something observable happened inside a cell. */
880
+ interface RunnerActivity {
881
+ cellId: string;
882
+ /** `waiting-visible`: the tab is hidden and the runner is waiting for it to come back before timing. */
883
+ phase: 'load' | 'warmup' | 'iteration' | 'quality' | 'reload' | 'waiting-visible';
884
+ /** Current iteration or quality item (1-based) and the total, where applicable. */
885
+ iteration?: number;
886
+ total?: number;
887
+ /** Characters and chunks streamed so far in the current generation. */
888
+ chars?: number;
889
+ chunks?: number;
890
+ /** Load progress percentage, where the provider reports one. */
891
+ pct?: number;
892
+ }
782
893
  /** Progress callbacks for a host UI. */
783
894
  interface RunnerHooks {
784
895
  /** The environment capture, before the fingerprint and the first cell (lets a host persist partial progress). */
@@ -787,6 +898,14 @@ interface RunnerHooks {
787
898
  onCellFinish?(cell: BenchCellResult): void;
788
899
  onLoadProgress?(cellId: string, pct: number | undefined): void;
789
900
  onIteration?(cellId: string, iteration: number, total: number): void;
901
+ /**
902
+ * Fires on every observable step inside a cell (each streamed chunk, each
903
+ * load progress event, each quality item), so a host can show that the run
904
+ * is alive and detect a stall at a glance.
905
+ */
906
+ onActivity?(activity: RunnerActivity): void;
907
+ /** A cell attempt failed and the runner is about to retry it (`attempt` is the one starting, 2-based). */
908
+ onCellRetry?(cellId: string, attempt: number, error: NonNullable<BenchCellResult['error']>): void;
790
909
  onPhase?(phase: string): void;
791
910
  }
792
911
  /** Inputs to a suite run. */
@@ -802,7 +921,11 @@ interface RunSuiteOptions {
802
921
  /** Skip the fingerprint microbenchmark (tests only; submissions require it). */
803
922
  skipFingerprint?: boolean;
804
923
  userReportedDevice?: string;
924
+ /** Trace recorder to use instead of a fresh one (tests drive its visibility). */
925
+ trace?: TraceLike;
805
926
  }
927
+ /** The trace recorder surface the runner needs. */
928
+ type TraceLike = Pick<TraceRecorder, 'attach' | 'dispose' | 'record' | 'isHidden' | 'pressureState' | 'all'>;
806
929
  /**
807
930
  * Run a benchmark suite and return the full, submittable result (raw traces
808
931
  * included; `clientSummaries` computed with the same code the server uses).
@@ -870,12 +993,16 @@ interface DeviceTypeSignals {
870
993
  formFactors?: string[];
871
994
  /** UA-CH `mobile` bit. */
872
995
  mobile?: boolean;
996
+ /** UA-CH `platform` (e.g. "Android"), which survives a "desktop site" UA rewrite. */
997
+ platform?: string;
873
998
  }
874
999
  /**
875
- * Derive a form factor. UA-CH form factors win when present; otherwise the UA
876
- * decides, with `maxTouchPoints` unmasking an iPad that reports itself as a
877
- * Mac (iPadOS 13+ default) and separating Android tablets (no `Mobile` token)
878
- * from phones.
1000
+ * Derive a form factor. A mobile operating system settles it first: Android
1001
+ * and iOS devices are phones or tablets whatever the form-factor hint says (an
1002
+ * unfolded Galaxy Z Fold sends `formFactors: ["Desktop"]` with a tablet-style
1003
+ * UA). Then UA-CH form factors; then the UA, with `maxTouchPoints` unmasking
1004
+ * an iPad that reports itself as a Mac (iPadOS 13+ default) and separating
1005
+ * Android tablets (no `Mobile` token) from phones.
879
1006
  *
880
1007
  * @example
881
1008
  * deriveDeviceType({ ua: navigator.userAgent, maxTouchPoints: navigator.maxTouchPoints });
@@ -973,37 +1100,6 @@ declare function runFingerprint(options?: {
973
1100
  minDurationMs?: number;
974
1101
  }): Promise<FingerprintResult>;
975
1102
 
976
- /**
977
- * Suite-level trace recorder: validity-relevant events (tab visibility, wake
978
- * lock, compute pressure, GPU device loss, aborts) with wall-clock timestamps.
979
- * Iterations that overlap a hidden period are invalidated, never retried
980
- * silently — the trace is part of the submitted, auditable record.
981
- */
982
-
983
- /** Records trace events and tracks the current validity-gate state. */
984
- declare class TraceRecorder {
985
- private readonly events;
986
- private disposers;
987
- private hidden;
988
- private lastPressureState;
989
- private wakeLockSentinel;
990
- /** Append an event at the current timestamp. */
991
- record(type: TraceEvent['type'], detail?: string): void;
992
- /** True while the page is hidden (timed regions overlapping this are invalid). */
993
- get isHidden(): boolean;
994
- /** Last observed compute-pressure state ('nominal'|'fair'|'serious'|'critical'). */
995
- get pressureState(): string | undefined;
996
- /** All recorded events (live reference; snapshot with `[...events]`). */
997
- get all(): readonly TraceEvent[];
998
- /**
999
- * Attach browser listeners (visibility, compute pressure) and request a
1000
- * screen wake lock. Safe to call outside a browser (records nothing).
1001
- */
1002
- attach(): Promise<void>;
1003
- /** Release listeners and the wake lock. */
1004
- dispose(): Promise<void>;
1005
- }
1006
-
1007
1103
  /**
1008
1104
  * Statistics for benchmark reporting: median headline, mean ± SD, IQR, 95% CI
1009
1105
  * (Student-t for the small n this protocol uses), CV, and geometric mean for
@@ -1143,7 +1239,7 @@ declare function computeRunDigest(result: BenchRunResult): Promise<string>;
1143
1239
  declare function verifyRunDigest(result: BenchRunResult): Promise<boolean>;
1144
1240
 
1145
1241
  /**
1146
- * Aggregation for the public leaderboard and for paper analysis. Runs group
1242
+ * Aggregation for the public leaderboard and for offline analysis. Runs group
1147
1243
  * into device-class rows; medians are taken per submission first, then across
1148
1244
  * submissions (median-of-medians). Nothing is ever averaged across devices.
1149
1245
  */
@@ -1192,7 +1288,7 @@ declare function aggregateRuns(runs: readonly BenchRunResult[], minSubmissions?:
1192
1288
  /** Leaderboard rows as CSV. */
1193
1289
  declare function rowsToCSV(rows: readonly LeaderboardRow[]): string;
1194
1290
  /**
1195
- * Long-format per-iteration CSV for paper analysis (one row per timed
1291
+ * Long-format per-iteration CSV for offline analysis (one row per timed
1196
1292
  * iteration, with full environment identity columns) — feed to R/pandas.
1197
1293
  */
1198
1294
  declare function runsToLongCSV(runs: readonly BenchRunResult[]): string;
@@ -1298,4 +1394,4 @@ interface STSPair {
1298
1394
  /** 100-pair STS-B test subset (order preserved from the source dataset). */
1299
1395
  declare const STSB_SUBSET: readonly STSPair[];
1300
1396
 
1301
- export { type APIAvailability, type AdapterAvailability, type AdapterLoadProgress, BENCH_PROTOCOL_VERSION, BENCH_SCHEMA_VERSION, type BenchCellResult, type BenchCellStatus, type BenchChunk, type BenchEmbeddingModel, type BenchLanguageModel, type BenchModelRef, type BenchRunResult, type BenchRuntimeId, type BenchStreamChunk, type BenchSuiteId, type BenchWorkloadKind, type BenchWorkloadSpec, type BrowserInfo, type CellSummary, type DeviceInfo, type DeviceType, type DeviceTypeSignals, type DisplayInfo, EMBED_WORKLOADS, type EmbedIteration, type EmbedWorkloadSpec, type EmbeddingRuntimeAdapter, type EnvironmentCapture, type FingerprintResult, GENERATION_BUDGET, type GPUInfo, HEADLINE_MIN_SUBMISSIONS, type HarnessInfo, type LLMIteration, type LLMRuntimeAdapter, type LLMWorkloadSpec, LLM_WORKLOADS, type LeaderboardRow, type LoadRecord, type LoadedEmbedder, type LoadedLLM, type LocaleInfo, MIN_GENERATED_CHARS, type MMLUItem, MMLU_MAX_TOKENS, MMLU_OUTPUT_CAP, type MemorySample, type MetricSummary, type NetworkInfo, type OSInfo, PLAUSIBILITY_RULES_VERSION, type PlannedCell, type PlausibilityFlag, type ProviderUsage, QUALITY_WORKLOADS, type QualityResult, type QualityWorkloadSpec, RUNTIME_EXECUTION_ORDER, RUN_POLICIES, type RunPolicy, type RunSuiteOptions, type RunnerHooks, STREAM_COHERENCE_MIN_SPAN_RATIO, STSB_SUBSET, type STSPair, TINY_MMLU, type TraceEvent, TraceRecorder, USAGE_FIDELITY, type ValidationReport, WORKLOADS_BY_ID, type WasmFeatureSupport, type WebGLInfo, aggregateRuns, canonicalJson, captureEnvironment, checkPlausibility, computeRunDigest, deriveDeviceType, detectEngine, detectWasmFeatures, deviceClassOf, formatMMLUPrompt, geomean, hrNow, inferTimerResolutionUs, isIncrementalStream, mean, median, memoryApiAvailable, orderCells, parseGpuModel, parseMMLUAnswer, parseUserAgent, quantile, resolveGpuModel, rowsToCSV, runBenchmarkSuite, runFingerprint, runMMLUFidelity, runSTSQuality, runsToLongCSV, sampleMemoryBytes, sha256Hex, sleep, spearman, stddev, summarize, summarizeCell, summarizeRun, validateRunShape, validateSubmission, verifyRunDigest };
1397
+ export { type APIAvailability, type AdapterAvailability, type AdapterLoadProgress, BENCH_PROTOCOL_VERSION, BENCH_SCHEMA_VERSION, type BenchCellResult, type BenchCellStatus, type BenchChunk, type BenchEmbeddingModel, type BenchLanguageModel, type BenchModelRef, type BenchRunResult, type BenchRuntimeId, type BenchStreamChunk, type BenchSuiteId, type BenchWorkloadKind, type BenchWorkloadSpec, type BrowserInfo, type CellSummary, type DeviceInfo, type DeviceType, type DeviceTypeSignals, type DisplayInfo, EMBED_WORKLOADS, type EmbedIteration, type EmbedWorkloadSpec, type EmbeddingRuntimeAdapter, type EnvironmentCapture, type FingerprintResult, GENERATION_BUDGET, type GPUInfo, HEADLINE_MIN_SUBMISSIONS, type HarnessInfo, type LLMIteration, type LLMRuntimeAdapter, type LLMWorkloadSpec, LLM_WORKLOADS, type LeaderboardRow, type LoadRecord, type LoadedEmbedder, type LoadedLLM, type LocaleInfo, MIN_GENERATED_CHARS, type MMLUItem, MMLU_MAX_TOKENS, MMLU_OUTPUT_CAP, type MemorySample, type MetricSummary, type NetworkInfo, type OSInfo, PLAUSIBILITY_RULES_VERSION, type PlannedCell, type PlausibilityFlag, type ProviderUsage, QUALITY_WORKLOADS, type QualityResult, type QualityWorkloadSpec, RUNTIME_EXECUTION_ORDER, RUN_POLICIES, type RunPolicy, type RunSuiteOptions, type RunnerActivity, type RunnerHooks, STREAM_COHERENCE_MIN_SPAN_RATIO, STSB_SUBSET, type STSPair, TINY_MMLU, type TraceEvent, TraceRecorder, USAGE_FIDELITY, type ValidationReport, WORKLOADS_BY_ID, type WasmFeatureSupport, type WebGLInfo, aggregateRuns, canonicalJson, captureEnvironment, checkPlausibility, computeRunDigest, deriveDeviceType, detectEngine, detectWasmFeatures, deviceClassOf, formatMMLUPrompt, geomean, hrNow, inferTimerResolutionUs, isIncrementalStream, mean, median, memoryApiAvailable, orderCells, parseGpuModel, parseMMLUAnswer, parseUserAgent, quantile, resolveGpuModel, rowsToCSV, runBenchmarkSuite, runFingerprint, runMMLUFidelity, runSTSQuality, runsToLongCSV, sampleMemoryBytes, sha256Hex, sleep, spearman, stddev, summarize, summarizeCell, summarizeRun, validateRunShape, validateSubmission, verifyRunDigest };