Spaces:
Runtime error
Runtime error
Warm/Cold tests, repeated tests
Browse files- README.md +7 -18
- bench-node/.gitignore +1 -0
- bench-node/README.md +6 -17
- bench-node/package-lock.json +5 -5
- bench-node/package.json +1 -1
- bench-node/src/index.ts +74 -29
- bench-web/README.md +5 -4
- bench-web/index.html +10 -3
- bench-web/package-lock.json +5 -5
- bench-web/package.json +1 -1
- bench-web/src/main.ts +101 -32
- bench-web/vite.config.ts +1 -6
README.md
CHANGED
|
@@ -1,30 +1,19 @@
|
|
| 1 |
-
# transformersjs-bench-min (
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
- `bench-
|
| 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 (
|
| 2 |
|
| 3 |
## Setup
|
| 4 |
```bash
|
|
@@ -6,22 +6,11 @@ cd bench-node
|
|
| 6 |
npm i
|
| 7 |
```
|
| 8 |
|
| 9 |
-
## Run
|
| 10 |
```bash
|
| 11 |
-
#
|
| 12 |
-
npm run bench
|
| 13 |
|
| 14 |
-
#
|
| 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.
|
| 4 |
"lockfileVersion": 3,
|
| 5 |
"requires": true,
|
| 6 |
"packages": {
|
| 7 |
"": {
|
| 8 |
"name": "bench-node",
|
| 9 |
-
"version": "0.0.
|
| 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.
|
| 996 |
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.6.
|
| 997 |
-
"integrity": "sha512-
|
| 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.
|
| 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 |
-
//
|
| 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 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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(
|
| 31 |
const t3 = performance.now();
|
| 32 |
|
| 33 |
-
|
| 34 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
platform: "node",
|
| 38 |
runtime: `node-${process.versions.node}`,
|
| 39 |
model: modelId,
|
| 40 |
task,
|
|
|
|
|
|
|
|
|
|
| 41 |
metrics: {
|
| 42 |
-
load_ms:
|
| 43 |
-
first_infer_ms:
|
| 44 |
}
|
| 45 |
-
}
|
|
|
|
|
|
|
| 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 (
|
| 2 |
|
| 3 |
## Setup
|
| 4 |
```bash
|
|
@@ -12,6 +12,7 @@ npm run dev
|
|
| 12 |
# open http://localhost:5173
|
| 13 |
```
|
| 14 |
|
| 15 |
-
|
| 16 |
-
-
|
| 17 |
-
-
|
|
|
|
|
|
| 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.
|
| 4 |
"lockfileVersion": 3,
|
| 5 |
"requires": true,
|
| 6 |
"packages": {
|
| 7 |
"": {
|
| 8 |
"name": "bench-web",
|
| 9 |
-
"version": "0.0.
|
| 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.
|
| 1311 |
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.6.
|
| 1312 |
-
"integrity": "sha512-
|
| 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.
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
}
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 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(
|
| 29 |
const t3 = now();
|
| 30 |
-
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
platform: "browser",
|
| 33 |
runtime: navigator.userAgent,
|
| 34 |
-
|
|
|
|
| 35 |
model: modelId,
|
| 36 |
task,
|
| 37 |
-
metrics
|
| 38 |
-
|
| 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 } });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|