whitphx HF Staff commited on
Commit
b4867cc
·
1 Parent(s): db3ea0b

Warm/Cold tests, repeated tests

Browse files
README.md CHANGED
@@ -1,30 +1,19 @@
1
- # transformersjs-bench-min (Minimal Template)
2
 
3
- This zip contains two tiny templates to benchmark model *load* and *first inference* times with Transformers.js.
4
-
5
- - `bench-node/`: Node.js CLI (WASM backend). Prints a JSON result to stdout.
6
- - `bench-web/`: Vite + TypeScript browser page. Shows a JSON result on screen.
7
 
8
  ## Quick start
9
-
10
- ### Node CLI
11
  ```bash
12
  cd bench-node
13
  npm i
14
- npm run bench
15
- # or model/task override:
16
- npm run bench -- Xenova/distilbert-base-uncased feature-extraction
17
  ```
18
-
19
- ### Browser app (Vite)
20
  ```bash
21
  cd bench-web
22
  npm i
23
  npm run dev
24
- # open http://localhost:5173 and click "Run benchmark"
25
  ```
26
-
27
- ## Notes
28
- - Models are fetched from the Hugging Face Hub/CDN the first time.
29
- - Browser backend selection (WebGPU/WASM) is handled internally by the library.
30
- - This matches your requested dependency versions.
 
1
+ # transformersjs-bench-min (warm/cold + repeats + p50/p90)
2
 
3
+ Includes:
4
+ - `bench-node/`: Node CLI with `--mode warm|cold`, `--repeats`, `--cache-dir`.
5
+ - `bench-web/`: Browser app with warm (prefetch+reload) / cold (clear caches) and repeats.
 
6
 
7
  ## Quick start
8
+ ### Node
 
9
  ```bash
10
  cd bench-node
11
  npm i
12
+ npm run bench -- Xenova/distilbert-base-uncased feature-extraction --mode warm --repeats 5 --cache-dir .bench-cache/warm
 
 
13
  ```
14
+ ### Web
 
15
  ```bash
16
  cd bench-web
17
  npm i
18
  npm run dev
 
19
  ```
 
 
 
 
 
bench-node/.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ .bench-cache
bench-node/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # bench-node (Transformers.js minimal benchmark)
2
 
3
  ## Setup
4
  ```bash
@@ -6,22 +6,11 @@ cd bench-node
6
  npm i
7
  ```
8
 
9
- ## Run
10
  ```bash
11
- # default: Xenova/distilbert-base-uncased + feature-extraction
12
- npm run bench
13
 
14
- # override model/task
15
- npm run bench -- Xenova/distilbert-base-uncased feature-extraction
16
- ```
17
-
18
- Output example:
19
- ```json
20
- {
21
- "platform": "node",
22
- "runtime": "node-22.x",
23
- "model": "Xenova/distilbert-base-uncased",
24
- "task": "feature-extraction",
25
- "metrics": { "load_ms": 1234.5, "first_infer_ms": 98.7 }
26
- }
27
  ```
 
1
+ # bench-node (warm/cold, repeats, p50/p90)
2
 
3
  ## Setup
4
  ```bash
 
6
  npm i
7
  ```
8
 
9
+ ## Run examples
10
  ```bash
11
+ # Warm: prefetch once (not measured) -> measure 5 times
12
+ npm run bench -- Xenova/distilbert-base-uncased feature-extraction --mode warm --repeats 5 --cache-dir .bench-cache/warm
13
 
14
+ # Cold: delete cache before each run, measure 3 times
15
+ npm run bench -- Xenova/distilbert-base-uncased feature-extraction --mode cold --repeats 3 --cache-dir .bench-cache/cold
 
 
 
 
 
 
 
 
 
 
 
16
  ```
bench-node/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
  {
2
  "name": "bench-node",
3
- "version": "0.0.1",
4
  "lockfileVersion": 3,
5
  "requires": true,
6
  "packages": {
7
  "": {
8
  "name": "bench-node",
9
- "version": "0.0.1",
10
  "dependencies": {
11
  "@huggingface/transformers": "^3.7.4"
12
  },
@@ -992,9 +992,9 @@
992
  "license": "BSD-3-Clause"
993
  },
994
  "node_modules/@types/node": {
995
- "version": "24.6.1",
996
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.6.1.tgz",
997
- "integrity": "sha512-ljvjjs3DNXummeIaooB4cLBKg2U6SPI6Hjra/9rRIy7CpM0HpLtG9HptkMKAb4HYWy5S7HUvJEuWgr/y0U8SHw==",
998
  "license": "MIT",
999
  "dependencies": {
1000
  "undici-types": "~7.13.0"
 
1
  {
2
  "name": "bench-node",
3
+ "version": "0.0.2",
4
  "lockfileVersion": 3,
5
  "requires": true,
6
  "packages": {
7
  "": {
8
  "name": "bench-node",
9
+ "version": "0.0.2",
10
  "dependencies": {
11
  "@huggingface/transformers": "^3.7.4"
12
  },
 
992
  "license": "BSD-3-Clause"
993
  },
994
  "node_modules/@types/node": {
995
+ "version": "24.6.2",
996
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.6.2.tgz",
997
+ "integrity": "sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang==",
998
  "license": "MIT",
999
  "dependencies": {
1000
  "undici-types": "~7.13.0"
bench-node/package.json CHANGED
@@ -2,7 +2,7 @@
2
  "name": "bench-node",
3
  "private": true,
4
  "type": "module",
5
- "version": "0.0.1",
6
  "scripts": {
7
  "bench": "tsx src/index.ts",
8
  "build": "tsc -p tsconfig.json"
 
2
  "name": "bench-node",
3
  "private": true,
4
  "type": "module",
5
+ "version": "0.0.2",
6
  "scripts": {
7
  "bench": "tsx src/index.ts",
8
  "build": "tsc -p tsconfig.json"
bench-node/src/index.ts CHANGED
@@ -1,51 +1,96 @@
1
- import { pipeline } from "@huggingface/transformers";
2
  import { performance } from "node:perf_hooks";
 
 
3
 
4
- // Minimal Node benchmark:
5
- // Measures model load time and first inference latency.
6
- // Default model/task can be overridden by CLI args.
7
- //
8
- // Usage:
9
- // npm run bench -- [model-id] [task]
10
- // Example:
11
- // npm run bench -- Xenova/distilbert-base-uncased feature-extraction
12
 
13
  const modelId = process.argv[2] || "Xenova/distilbert-base-uncased";
14
  const task = process.argv[3] || "feature-extraction";
15
 
16
- async function main() {
17
- console.log(`Model: ${modelId}`);
18
- console.log(`Task : ${task}`);
 
 
 
 
 
 
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  const t0 = performance.now();
21
- const pipe = await pipeline(task, modelId, {
22
- // You can tweak backend settings here if needed.
23
- // For Node, WASM backend is used by default.
24
- });
25
  const t1 = performance.now();
26
 
27
- const input = "The quick brown fox jumps over the lazy dog.";
28
-
29
  const t2 = performance.now();
30
- await pipe(input);
31
  const t3 = performance.now();
32
 
33
- const loadMs = (t1 - t0).toFixed(1);
34
- const firstInferMs = (t3 - t2).toFixed(1);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
- console.log(JSON.stringify({
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  platform: "node",
38
  runtime: `node-${process.versions.node}`,
39
  model: modelId,
40
  task,
 
 
 
41
  metrics: {
42
- load_ms: Number(loadMs),
43
- first_infer_ms: Number(firstInferMs),
44
  }
45
- }, null, 2));
 
 
46
  }
47
 
48
- main().catch((e) => {
49
- console.error(e);
50
- process.exit(1);
51
- });
 
1
+ import { pipeline, env } from "@huggingface/transformers";
2
  import { performance } from "node:perf_hooks";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
 
6
+ // Node benchmark with warm/cold modes, repeats, p50/p90
 
 
 
 
 
 
 
7
 
8
  const modelId = process.argv[2] || "Xenova/distilbert-base-uncased";
9
  const task = process.argv[3] || "feature-extraction";
10
 
11
+ function getArg(name: string, def?: string) {
12
+ const i = process.argv.indexOf(`--${name}`);
13
+ if (i !== -1 && i + 1 < process.argv.length) return process.argv[i + 1];
14
+ return def;
15
+ }
16
+
17
+ const mode = (getArg("mode", "warm") as "warm" | "cold");
18
+ const repeats = Math.max(1, parseInt(getArg("repeats", "3") || "3", 10));
19
+ const cacheDir = getArg("cache-dir", path.resolve(".bench-cache/default"))!;
20
 
21
+ // Point library cache to a dedicated directory for controllable cold/warm behavior
22
+ env.cacheDir = cacheDir;
23
+
24
+ function ensureEmptyDir(dir: string) {
25
+ if (fs.existsSync(dir)) fs.rmSync(dir, { recursive: true, force: true });
26
+ fs.mkdirSync(dir, { recursive: true });
27
+ }
28
+
29
+ function percentile(values: number[], q: number) {
30
+ const a = [...values].sort((x, y) => x - y);
31
+ const i = (a.length - 1) * q;
32
+ const i0 = Math.floor(i), i1 = Math.ceil(i);
33
+ return i0 === i1 ? a[i0] : a[i0] + (a[i1] - a[i0]) * (i - i0);
34
+ }
35
+
36
+ async function benchOnce() {
37
  const t0 = performance.now();
38
+ const pipe = await pipeline(task, modelId, {});
 
 
 
39
  const t1 = performance.now();
40
 
 
 
41
  const t2 = performance.now();
42
+ await pipe("The quick brown fox jumps over the lazy dog.");
43
  const t3 = performance.now();
44
 
45
+ return { load_ms: +(t1 - t0).toFixed(1), first_infer_ms: +(t3 - t2).toFixed(1) };
46
+ }
47
+
48
+ async function main() {
49
+ console.log(`Model : ${modelId}`);
50
+ console.log(`Task : ${task}`);
51
+ console.log(`Mode : ${mode}`);
52
+ console.log(`Repeats: ${repeats}`);
53
+ console.log(`Cache : ${cacheDir}`);
54
+
55
+ const loads: number[] = [];
56
+ const firsts: number[] = [];
57
+
58
+ if (mode === "warm") {
59
+ // Fresh cache dir, prefetch once (not measured), then measure N times
60
+ ensureEmptyDir(cacheDir);
61
+ const warm = await pipeline(task, modelId, {});
62
+ await warm("warmup");
63
 
64
+ for (let i = 0; i < repeats; i++) {
65
+ const r = await benchOnce();
66
+ loads.push(r.load_ms);
67
+ firsts.push(r.first_infer_ms);
68
+ }
69
+ } else {
70
+ // cold: delete cache dir before each measured run
71
+ for (let i = 0; i < repeats; i++) {
72
+ ensureEmptyDir(cacheDir);
73
+ const r = await benchOnce();
74
+ loads.push(r.load_ms);
75
+ firsts.push(r.first_infer_ms);
76
+ }
77
+ }
78
+
79
+ const result = {
80
  platform: "node",
81
  runtime: `node-${process.versions.node}`,
82
  model: modelId,
83
  task,
84
+ mode,
85
+ repeats,
86
+ cacheDir,
87
  metrics: {
88
+ load_ms: { p50: +percentile(loads, 0.5).toFixed(1), p90: +percentile(loads, 0.9).toFixed(1), raw: loads },
89
+ first_infer_ms: { p50: +percentile(firsts, 0.5).toFixed(1), p90: +percentile(firsts, 0.9).toFixed(1), raw: firsts }
90
  }
91
+ };
92
+
93
+ console.log(JSON.stringify(result, null, 2));
94
  }
95
 
96
+ main().catch((e) => { console.error(e); process.exit(1); });
 
 
 
bench-web/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # bench-web (Transformers.js minimal browser benchmark)
2
 
3
  ## Setup
4
  ```bash
@@ -12,6 +12,7 @@ npm run dev
12
  # open http://localhost:5173
13
  ```
14
 
15
- - Pick a model/task (default `Xenova/distilbert-base-uncased` + `feature-extraction`), click "Run benchmark".
16
- - The page prints a small JSON with load time and first inference latency.
17
- - Works with WASM by default. If your browser supports WebGPU, the library may use it automatically.
 
 
1
+ # bench-web (warm/cold, repeats, p50/p90)
2
 
3
  ## Setup
4
  ```bash
 
12
  # open http://localhost:5173
13
  ```
14
 
15
+ ## How it works
16
+ - **warm**: prefetch once (non-measured) auto-reload measure `repeats` times with disk caches populated.
17
+ - **cold**: clear Cache Storage & IndexedDB, then measure in the same tab
18
+ - Note: only the 1st iteration is strictly cold within a single page session.
bench-web/index.html CHANGED
@@ -8,7 +8,7 @@
8
  body { font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; margin: 2rem; }
9
  button { padding: 0.6rem 1rem; font-size: 1rem; }
10
  pre { background: #f6f8fa; padding: 1rem; border-radius: 8px; overflow: auto; }
11
- .row { display: flex; gap: 0.5rem; align-items: center; margin-bottom: 0.5rem; }
12
  label { font-weight: 600; }
13
  input, select { padding: 0.4rem 0.6rem; }
14
  </style>
@@ -18,14 +18,21 @@
18
  <div class="row">
19
  <label for="model">Model</label>
20
  <input id="model" value="Xenova/distilbert-base-uncased" size="34" />
21
- </div>
22
- <div class="row">
23
  <label for="task">Task</label>
24
  <select id="task">
25
  <option value="feature-extraction" selected>feature-extraction</option>
26
  <option value="text-classification">text-classification</option>
27
  </select>
28
  </div>
 
 
 
 
 
 
 
 
 
29
  <div class="row">
30
  <button id="run">Run benchmark</button>
31
  <span id="status"></span>
 
8
  body { font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; margin: 2rem; }
9
  button { padding: 0.6rem 1rem; font-size: 1rem; }
10
  pre { background: #f6f8fa; padding: 1rem; border-radius: 8px; overflow: auto; }
11
+ .row { display: flex; gap: 0.5rem; align-items: center; margin-bottom: 0.5rem; flex-wrap: wrap; }
12
  label { font-weight: 600; }
13
  input, select { padding: 0.4rem 0.6rem; }
14
  </style>
 
18
  <div class="row">
19
  <label for="model">Model</label>
20
  <input id="model" value="Xenova/distilbert-base-uncased" size="34" />
 
 
21
  <label for="task">Task</label>
22
  <select id="task">
23
  <option value="feature-extraction" selected>feature-extraction</option>
24
  <option value="text-classification">text-classification</option>
25
  </select>
26
  </div>
27
+ <div class="row">
28
+ <label for="mode">Mode</label>
29
+ <select id="mode">
30
+ <option value="warm" selected>warm (prefetch + reload)</option>
31
+ <option value="cold">cold (clear caches once)</option>
32
+ </select>
33
+ <label for="repeats">Repeats</label>
34
+ <input id="repeats" type="number" value="3" min="1" style="width: 5rem;" />
35
+ </div>
36
  <div class="row">
37
  <button id="run">Run benchmark</button>
38
  <span id="status"></span>
bench-web/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
  {
2
  "name": "bench-web",
3
- "version": "0.0.1",
4
  "lockfileVersion": 3,
5
  "requires": true,
6
  "packages": {
7
  "": {
8
  "name": "bench-web",
9
- "version": "0.0.1",
10
  "dependencies": {
11
  "@huggingface/transformers": "^3.7.4"
12
  },
@@ -1307,9 +1307,9 @@
1307
  "license": "MIT"
1308
  },
1309
  "node_modules/@types/node": {
1310
- "version": "24.6.1",
1311
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.6.1.tgz",
1312
- "integrity": "sha512-ljvjjs3DNXummeIaooB4cLBKg2U6SPI6Hjra/9rRIy7CpM0HpLtG9HptkMKAb4HYWy5S7HUvJEuWgr/y0U8SHw==",
1313
  "license": "MIT",
1314
  "dependencies": {
1315
  "undici-types": "~7.13.0"
 
1
  {
2
  "name": "bench-web",
3
+ "version": "0.0.2",
4
  "lockfileVersion": 3,
5
  "requires": true,
6
  "packages": {
7
  "": {
8
  "name": "bench-web",
9
+ "version": "0.0.2",
10
  "dependencies": {
11
  "@huggingface/transformers": "^3.7.4"
12
  },
 
1307
  "license": "MIT"
1308
  },
1309
  "node_modules/@types/node": {
1310
+ "version": "24.6.2",
1311
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.6.2.tgz",
1312
+ "integrity": "sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang==",
1313
  "license": "MIT",
1314
  "dependencies": {
1315
  "undici-types": "~7.13.0"
bench-web/package.json CHANGED
@@ -1,7 +1,7 @@
1
  {
2
  "name": "bench-web",
3
  "private": true,
4
- "version": "0.0.1",
5
  "type": "module",
6
  "scripts": {
7
  "dev": "vite",
 
1
  {
2
  "name": "bench-web",
3
  "private": true,
4
+ "version": "0.0.2",
5
  "type": "module",
6
  "scripts": {
7
  "dev": "vite",
bench-web/src/main.ts CHANGED
@@ -5,49 +5,118 @@ const out = document.getElementById("out") as HTMLPreElement;
5
  const statusEl = document.getElementById("status") as HTMLSpanElement;
6
  const modelEl = document.getElementById("model") as HTMLInputElement;
7
  const taskEl = document.getElementById("task") as HTMLSelectElement;
 
 
8
 
9
- function now() {
10
- return performance.now();
 
 
 
 
11
  }
12
-
13
- async function run() {
14
- const modelId = modelEl.value.trim() || "Xenova/distilbert-base-uncased";
15
- const task = taskEl.value;
16
- statusEl.textContent = "loading...";
17
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  const t0 = now();
19
- const pipe = await pipeline(task, modelId, {
20
- // For browser, the library will pick the best available backend (WebGPU/WASM).
21
- });
22
  const t1 = now();
23
-
24
- const input = "The quick brown fox jumps over the lazy dog.";
25
- statusEl.textContent = "running inference...";
26
-
27
  const t2 = now();
28
- await pipe(input);
29
  const t3 = now();
30
-
31
- const result = {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  platform: "browser",
33
  runtime: navigator.userAgent,
34
- backend_hint: ("gpu" in navigator) ? "webgpu-or-wasm" : "wasm",
 
35
  model: modelId,
36
  task,
37
- metrics: {
38
- load_ms: +(t1 - t0).toFixed(1),
39
- first_infer_ms: +(t3 - t2).toFixed(1),
40
- }
41
  };
42
-
43
- out.textContent = JSON.stringify(result, null, 2);
44
- statusEl.textContent = "done";
45
  }
46
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  btn.addEventListener("click", () => {
48
- run().catch((e) => {
49
- out.textContent = String(e);
50
- statusEl.textContent = "error";
51
- console.error(e);
52
- });
53
  });
 
5
  const statusEl = document.getElementById("status") as HTMLSpanElement;
6
  const modelEl = document.getElementById("model") as HTMLInputElement;
7
  const taskEl = document.getElementById("task") as HTMLSelectElement;
8
+ const modeEl = document.getElementById("mode") as HTMLSelectElement;
9
+ const repeatsEl = document.getElementById("repeats") as HTMLInputElement;
10
 
11
+ function now() { return performance.now(); }
12
+ function percentile(values: number[], q: number) {
13
+ const a = [...values].sort((x, y) => x - y);
14
+ const i = (a.length - 1) * q;
15
+ const i0 = Math.floor(i), i1 = Math.ceil(i);
16
+ return i0 === i1 ? a[i0] : a[i0] + (a[i1] - a[i0]) * (i - i0);
17
  }
18
+ async function clearCaches({ clearSession=false }: { clearSession?: boolean } = {}) {
19
+ try {
20
+ const keys = await caches.keys();
21
+ await Promise.all(keys.map((k) => caches.delete(k)));
22
+ } catch {}
23
+ try {
24
+ const anyIDB: any = indexedDB as any;
25
+ if (typeof anyIDB.databases === "function") {
26
+ const dbs = await anyIDB.databases();
27
+ await Promise.all(dbs.map((d: any) => d?.name ? indexedDB.deleteDatabase(d.name) : undefined));
28
+ } else {
29
+ indexedDB.deleteDatabase("transformers-cache");
30
+ indexedDB.deleteDatabase("model-cache");
31
+ }
32
+ } catch {}
33
+ try {
34
+ localStorage.clear();
35
+ if (clearSession) sessionStorage.clear();
36
+ } catch {}
37
+ }
38
+ async function benchOnce(modelId: string, task: string) {
39
  const t0 = now();
40
+ const pipe = await pipeline(task, modelId, {});
 
 
41
  const t1 = now();
 
 
 
 
42
  const t2 = now();
43
+ await pipe("The quick brown fox jumps over the lazy dog.");
44
  const t3 = now();
45
+ return { load_ms: +(t1 - t0).toFixed(1), first_infer_ms: +(t3 - t2).toFixed(1) };
46
+ }
47
+ async function runMany(modelId: string, task: string, repeats: number) {
48
+ const loads: number[] = [];
49
+ const firsts: number[] = [];
50
+ for (let i = 0; i < repeats; i++) {
51
+ const r = await benchOnce(modelId, task);
52
+ loads.push(r.load_ms);
53
+ firsts.push(r.first_infer_ms);
54
+ }
55
+ return {
56
+ load_ms: { p50: +percentile(loads, 0.5).toFixed(1), p90: +percentile(loads, 0.9).toFixed(1), raw: loads },
57
+ first_infer_ms: { p50: +percentile(firsts, 0.5).toFixed(1), p90: +percentile(firsts, 0.9).toFixed(1), raw: firsts },
58
+ };
59
+ }
60
+ async function runCold(modelId: string, task: string, repeats: number) {
61
+ statusEl.textContent = "clearing caches (cold)...";
62
+ await clearCaches();
63
+ statusEl.textContent = "running (cold)...";
64
+ const metrics = await runMany(modelId, task, repeats);
65
+ return {
66
  platform: "browser",
67
  runtime: navigator.userAgent,
68
+ mode: "cold",
69
+ repeats,
70
  model: modelId,
71
  task,
72
+ metrics,
73
+ notes: "Only the 1st iteration is strictly cold in a single page session."
 
 
74
  };
 
 
 
75
  }
76
+ async function runWarm(modelId: string, task: string, repeats: number) {
77
+ const flag = sessionStorage.getItem("__warm_ready__");
78
+ if (!flag) {
79
+ statusEl.textContent = "prefetching (warmup) ...";
80
+ const p = await pipeline(task, modelId, {});
81
+ await p("warmup");
82
+ sessionStorage.setItem("__warm_ready__", JSON.stringify({ modelId, task, repeats }));
83
+ location.reload();
84
+ return null;
85
+ } else {
86
+ sessionStorage.removeItem("__warm_ready__");
87
+ statusEl.textContent = "running (warm)...";
88
+ const metrics = await runMany(modelId, task, repeats);
89
+ return {
90
+ platform: "browser",
91
+ runtime: navigator.userAgent,
92
+ mode: "warm",
93
+ repeats,
94
+ model: modelId,
95
+ task,
96
+ metrics
97
+ };
98
+ }
99
+ }
100
+ async function run() {
101
+ const modelId = modelEl.value.trim() || "Xenova/distilbert-base-uncased";
102
+ const task = taskEl.value;
103
+ const mode = modeEl.value as "warm" | "cold";
104
+ const repeats = Math.max(1, parseInt(repeatsEl.value || "3", 10));
105
+ out.textContent = "{}";
106
+ if (mode === "cold") {
107
+ const r = await runCold(modelId, task, repeats);
108
+ if (r) { out.textContent = JSON.stringify(r, null, 2); statusEl.textContent = "done (cold)"; }
109
+ } else {
110
+ const r = await runWarm(modelId, task, repeats);
111
+ if (r) { out.textContent = JSON.stringify(r, null, 2); statusEl.textContent = "done (warm)"; }
112
+ }
113
+ }
114
+ (async () => {
115
+ const flag = sessionStorage.getItem("__warm_ready__");
116
+ if (flag) {
117
+ try { await run(); } catch (e) { console.error(e); }
118
+ }
119
+ })();
120
  btn.addEventListener("click", () => {
121
+ run().catch((e) => { out.textContent = String(e); statusEl.textContent = "error"; console.error(e); });
 
 
 
 
122
  });
bench-web/vite.config.ts CHANGED
@@ -1,7 +1,2 @@
1
  import { defineConfig } from "vite";
2
-
3
- export default defineConfig({
4
- server: {
5
- port: 5173
6
- }
7
- });
 
1
  import { defineConfig } from "vite";
2
+ export default defineConfig({ server: { port: 5173 } });