webgpu-bench-cli 0.8.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ben Houston
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # webgpu-bench
2
+
3
+ [![CI](https://github.com/bhouston/webgpu-bench/actions/workflows/ci.yml/badge.svg)](https://github.com/bhouston/webgpu-bench/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/webgpu-bench.svg)](https://www.npmjs.com/package/webgpu-bench)
5
+ [![npm downloads](https://img.shields.io/npm/dm/webgpu-bench.svg)](https://www.npmjs.com/package/webgpu-bench)
6
+ [![Live demo](https://img.shields.io/badge/demo-live-brightgreen)](https://web3dsurvey.com/benchmark)
7
+
8
+ A microbenchmark suite for WebGPU: it isolates and benchmarks a device's raw ceilings — memory
9
+ **bandwidth** (read/write) and **FLOPS** (fp32, fp16, int8; scalar/vec4/mat4/matvec) — one operation at a
10
+ time in the browser, not a full app or game.
11
+
12
+ Try it live on the [Web3D Survey: GPU Benchmark page](https://web3dsurvey.com/benchmark).
13
+
14
+ ## Command line
15
+
16
+ Run the whole suite on a machine's GPU without a browser (WebGPU via [Dawn](https://github.com/dawn-gpu/node-webgpu)):
17
+
18
+ ```sh
19
+ npx webgpu-bench-cli # or: npm i -g webgpu-bench-cli && webgpu-bench
20
+ webgpu-bench --filter 'f16-*' # glob on benchmark ids
21
+ webgpu-bench --no-report # don't submit the run to web3dsurvey.com
22
+ webgpu-bench --json # machine-readable output
23
+ ```
24
+
25
+ Progress is printed as a percentage on stderr; the results table goes to stdout once the run completes.
26
+ By default a finished run is submitted to [Web3D Survey](https://web3dsurvey.com/benchmark) and its result
27
+ page URL is printed.
28
+
29
+ ## Usage
30
+
31
+ `runSuite` (in `suite.ts`) is a scheduler: it round-robins the given benchmarks, times them, watches for
32
+ thermal throttling, and yields result rows. `BENCHMARKS` (in `catalog.ts`) is this package's own set of
33
+ definitions and the default; pass your own `BenchmarkDefinition`s, a filtered subset, or a mix:
34
+
35
+ ```ts
36
+ import { runSuite, BENCHMARKS, type BenchmarkDefinition } from 'webgpu-bench';
37
+
38
+ const myKernel: BenchmarkDefinition = {
39
+ id: 'my-kernel',
40
+ label: 'My kernel',
41
+ description: '...',
42
+ source: myWgsl,
43
+ category: 'compute',
44
+ metric: { key: 'flops', unit: 'FLOP', name: 'Floating-point ops' },
45
+ prepare: async ({ ctx, harness }) => {
46
+ /* build a pipeline/bind group, return via prepareKernelBenchmark(...) */
47
+ },
48
+ };
49
+
50
+ for await (const result of runSuite({
51
+ benchmarks: [...BENCHMARKS.filter((b) => b.category === 'bandwidth'), myKernel],
52
+ })) {
53
+ // ...
54
+ }
55
+ ```
56
+
57
+ `index.ts` also exports `GpuContext`/`acquireGpuContext`, `createPipeline`, `prepareKernelBenchmark`, the
58
+ shared metric defs, and `generateMatVecData` for writing your own `prepare()`.
59
+
60
+ ## What's measured
61
+
62
+ Each benchmark isolates one resource — memory read, memory write, or ALU — keeping the others near zero,
63
+ so numbers compare directly against a device's published bandwidth/FLOPS specs.
64
+
65
+ | Benchmark | What it tests |
66
+ | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
67
+ | `read-linear` / `write-linear` | Coalesced grid-stride streaming of a 4096×4096 f32 buffer, isolating read or write throughput |
68
+ | `read-gather-16kb` / `-4mb` / `-64mb` | Same byte count, but each load hits a pseudo-random vec4 inside a 16 KB (L1), 4 MB (L2), or 64 MB (DRAM, the whole buffer) window |
69
+ | `write-scatter-16kb` / `-4mb` / `-64mb` | Same byte count, but each store lands on a pseudo-random vec4 inside the same three windows |
70
+ | `f32-fma-scalar` / `-vec4` / `-mat4` / `-matvec` | Unrolled independent FMA chains in fp32, at scalar/vec4/mat4/register-resident-matvec granularity |
71
+ | `f16-fma-*` | The same four shapes in `f16` (skipped without `shader-f16`) |
72
+ | `i32-mad-*` | The same four shapes as 32-bit integer multiply-add (WGSL has no `i8` arithmetic) |
73
+ | `i8-dp4a` / `i8-dp4a-matvec` / `u8-dp4a` | Packed int8 dot products via `dot4I8Packed` / `dot4U8Packed` (skipped without the feature) |
74
+ | `u32-shift` | Variable-amount u32 shifts (left and right) cycling through all 32 amounts; the average shift throughput |
75
+
76
+ Loop trip counts are runtime values so the shader compiler can't fold them away, and are calibrated per
77
+ GPU (see below) so each dispatch stays short.
78
+
79
+ ## Sampling methodology
80
+
81
+ Benchmarks run round-robin, not one-after-another, to avoid thermal throttling from skewing later
82
+ results. Per benchmark, the suite:
83
+
84
+ 1. Prepares all kernels up front, so unsupported ones resolve as `skipped` immediately.
85
+ 2. Calibrates dispatch size so one dispatch takes ~`targetDispatchMs` (10ms) — never freezing the tab.
86
+ 3. Takes short (~100ms) timed measurements in random order, with an idle gap between them.
87
+ 4. Reports the best (fastest) run — noise only ever slows a measurement down, never speeds it up.
88
+ 5. Converges once the best stops improving (`minRounds`/`stableRounds`), up to a `maxRounds` cap.
89
+ 6. Discards runs >20% slower than the current best as thermal noise (`throttledMs`, not `stats`).
90
+ 7. Pauses and retries if the device is broadly throttled; gives up after `maxCooldowns` and flags the row.
91
+
92
+ All of the above are tunable via `SuiteOptions`. Timing prefers GPU `timestamp-query` and falls back to
93
+ wall-clock, cross-checking one against the other every measurement (Safari's timestamps are unreliable
94
+ enough to need this). `BenchmarkResult.stats` also has mean/median/stddev/ci95 if you want more than the
95
+ best-run headline number.
96
+
97
+ ## Monorepo layout
98
+
99
+ - `packages/webgpu-bench` — the benchmark suite (WGSL shaders, data generation, timing harness,
100
+ orchestration). Framework-agnostic, browser-only, consumed as TypeScript source.
101
+ - `packages/cli` — `webgpu-bench` command-line runner (WebGPU via Dawn, no browser needed).
102
+
103
+ ## Development
104
+
105
+ ```bash
106
+ pnpm install
107
+ pnpm build
108
+ pnpm lint
109
+ pnpm tsc
110
+ ```
111
+
112
+ Requires a WebGPU-capable browser (recent Chrome/Edge desktop). `f16`/`int8`-dot-product benchmarks report
113
+ as "skipped" rather than failing when a device lacks the feature.
114
+
115
+ ## Author
116
+
117
+ Created by [Ben Houston](https://ben3d.ca) and sponsored by [Land of Assets](https://landofassets.com).
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import './instrument.ts';
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,iBAAiB,CAAC"}
package/dist/cli.js ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ import "./instrument.js";
3
+ import * as Sentry from '@sentry/node';
4
+ import { createRequire } from 'node:module';
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import yargs from 'yargs';
8
+ import { hideBin } from 'yargs/helpers';
9
+ import { fileCommands } from 'yargs-file-commands';
10
+ const { version } = createRequire(import.meta.url)('../package.json');
11
+ const commandsDir = path.join(path.dirname(fileURLToPath(import.meta.url)), 'commands');
12
+ try {
13
+ await yargs(hideBin(process.argv))
14
+ .scriptName('webgpu-bench')
15
+ .version(version)
16
+ .command(await fileCommands({ commandDirs: [commandsDir] }))
17
+ .strict()
18
+ .fail(false)
19
+ .wrap(100)
20
+ .help().argv;
21
+ }
22
+ catch (error) {
23
+ Sentry.captureException(error);
24
+ await Sentry.flush(2000);
25
+ console.error(error instanceof Error ? error.message : error);
26
+ process.exitCode = 1;
27
+ }
28
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,iBAAiB,CAAC;AAEzB,OAAO,KAAK,MAAM,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,MAAM,EAAE,OAAO,EAAE,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAwB,CAAC;AAC7F,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;AAExF,IAAI,CAAC;IACH,MAAM,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;SAC/B,UAAU,CAAC,cAAc,CAAC;SAC1B,OAAO,CAAC,OAAO,CAAC;SAChB,OAAO,CAAC,MAAM,YAAY,CAAC,EAAE,WAAW,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;SAC3D,MAAM,EAAE;SACR,IAAI,CAAC,KAAK,CAAC;SACX,IAAI,CAAC,GAAG,CAAC;SACT,IAAI,EAAE,CAAC,IAAI,CAAC;AACjB,CAAC;AAAC,OAAO,KAAK,EAAE,CAAC;IACf,MAAM,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAC/B,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzB,OAAO,CAAC,KAAK,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAC9D,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC"}
@@ -0,0 +1,10 @@
1
+ export declare const command: import("yargs-file-commands").DefineCommandResult<{}, {
2
+ filter: string | undefined;
3
+ } & {
4
+ report: boolean;
5
+ } & {
6
+ json: boolean;
7
+ } & {
8
+ "api-host": string;
9
+ }>;
10
+ //# sourceMappingURL=$default.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"$default.d.ts","sourceRoot":"","sources":["../../src/commands/$default.ts"],"names":[],"mappings":"AAwEA,eAAO,MAAM,OAAO;;;;;;;;EAyElB,CAAC"}
@@ -0,0 +1,131 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { createRequire } from 'node:module';
3
+ import os from 'node:os';
4
+ import * as Sentry from '@sentry/node';
5
+ import chalk from 'chalk';
6
+ import { create, globals } from 'webgpu';
7
+ import { BENCHMARKS, runSuite, SuiteProgress } from 'webgpu-bench';
8
+ import { defineCommand } from 'yargs-file-commands';
9
+ import { formatTable, globToRegExp } from "../format.js";
10
+ const { version } = createRequire(import.meta.url)('../../package.json');
11
+ // web3dsurvey's expected data version — keep in sync with
12
+ // web3dsurvey/packages/shared/src/benchDataVersion.ts (bumped when what's measured changes).
13
+ const BENCH_DATA_VERSION = 30;
14
+ const SITE = 'https://web3dsurvey.com';
15
+ const BAR_WIDTH = 40;
16
+ const PLATFORM_NAMES = { Darwin: 'Mac OS', Windows_NT: 'Windows', Linux: 'Linux' };
17
+ const PLATFORM = PLATFORM_NAMES[os.type()] ?? os.type();
18
+ // Parsed server-side by web3dsurvey's bench handler — keep the shape in sync with its CLI_USER_AGENT regex.
19
+ // ponytail: os.release() is the kernel version (Darwin 27.0.0), not the marketing one; good enough to group by.
20
+ const USER_AGENT = `webgpu-bench-cli/${version} (${PLATFORM} ${os.release()}; ${os.arch()}; Node.js ${process.versions.node})`;
21
+ const KEY_LIMITS = [
22
+ 'maxBufferSize',
23
+ 'maxStorageBufferBindingSize',
24
+ 'maxComputeWorkgroupSizeX',
25
+ 'maxComputeInvocationsPerWorkgroup',
26
+ 'maxComputeWorkgroupStorageSize',
27
+ ];
28
+ function formatDeviceInfo(info) {
29
+ const row = (k, v) => `${chalk.dim(k.padEnd(14))}${v}`;
30
+ const yesNo = (b) => (b ? chalk.green('yes') : chalk.yellow('no'));
31
+ return [
32
+ row('Vendor', chalk.bold(info.vendor ?? 'unknown')),
33
+ row('Architecture', chalk.bold(info.architecture ?? 'unknown')),
34
+ row('Description', info.description ?? 'unknown'),
35
+ row('Features', info.features.join(', ') || 'none'),
36
+ row('Limits', KEY_LIMITS.map((k) => `${k}=${info.limits[k] ?? '?'}`).join(', ')),
37
+ row('Supports', `f16 ${yesNo(info.supportsF16)}, packed i8 dot ${yesNo(info.supportsI8Dot)}, GPU timestamps ${yesNo(info.supportsTimestampQuery)}`),
38
+ row('Host', `${PLATFORM} ${os.release()} ${os.arch()}, Node.js ${process.versions.node}, Dawn`),
39
+ ].join('\n');
40
+ }
41
+ async function report(apiHost, info, results) {
42
+ const id = randomUUID();
43
+ const body = {
44
+ id,
45
+ version: BENCH_DATA_VERSION,
46
+ adapterInfo: info && { vendor: info.vendor, architecture: info.architecture, description: info.description },
47
+ results: results.filter((r) => r.status === 'ok').map((r) => ({ id: r.id, metricValue: r.metricValue })),
48
+ };
49
+ const response = await fetch(`${apiHost}/api/bench?shareable=true`, {
50
+ method: 'POST',
51
+ headers: { 'Content-Type': 'application/json', 'User-Agent': USER_AGENT },
52
+ body: JSON.stringify(body),
53
+ signal: AbortSignal.timeout(20_000),
54
+ });
55
+ // The route's only success response is 204; a 200 is the API politely ignoring us as a bot.
56
+ if (response.status !== 204)
57
+ throw new Error(`Report rejected: HTTP ${response.status} ${await response.text()}`);
58
+ return id;
59
+ }
60
+ export const command = defineCommand({
61
+ describe: "Benchmark this machine's GPU via WebGPU (Dawn), no browser needed",
62
+ builder: (yargs) => yargs
63
+ .option('filter', {
64
+ type: 'string',
65
+ describe: 'Only run benchmarks whose id matches this glob, e.g. "f16-*" or "read-*"',
66
+ })
67
+ .option('report', {
68
+ type: 'boolean',
69
+ default: true,
70
+ describe: `Submit results to ${SITE} (--no-report to keep them local)`,
71
+ })
72
+ .option('json', { type: 'boolean', default: false, describe: 'Print results as JSON instead of a table' })
73
+ .option('api-host', { type: 'string', default: 'https://api.web3dsurvey.com', hidden: true }),
74
+ handler: async (argv) => {
75
+ const benchmarks = argv.filter ? BENCHMARKS.filter((b) => globToRegExp(argv.filter).test(b.id)) : BENCHMARKS;
76
+ if (benchmarks.length === 0) {
77
+ throw new Error(`No benchmarks match "${argv.filter}". Ids: ${BENCHMARKS.map((b) => b.id).join(', ')}`);
78
+ }
79
+ // Dawn's node binding: GPUBufferUsage & co. become globals, navigator.gpu is
80
+ // defined onto Node's built-in (getter-only) navigator.
81
+ Object.assign(globalThis, globals);
82
+ Object.defineProperty(navigator, 'gpu', { value: create([]), configurable: true });
83
+ console.error(`${chalk.bold('webgpu-bench-cli')} ${chalk.dim(`v${version}`)}`);
84
+ const progress = new SuiteProgress(benchmarks.length);
85
+ const results = new Map();
86
+ let info;
87
+ let lastPercent = -1;
88
+ const tty = process.stderr.isTTY;
89
+ const bar = (fraction) => {
90
+ const filled = Math.round(fraction * BAR_WIDTH);
91
+ return `[${chalk.green('█'.repeat(filled))}${chalk.dim('░'.repeat(BAR_WIDTH - filled))}]`;
92
+ };
93
+ const onDeviceInfo = (i) => {
94
+ info = i;
95
+ if (!argv.json)
96
+ console.log(`${formatDeviceInfo(i)}\n`);
97
+ console.error(`Running ${benchmarks.length} benchmark${benchmarks.length === 1 ? '' : 's'}…`);
98
+ };
99
+ for await (const r of runSuite({ benchmarks, onDeviceInfo, onProgress: progress.onProgress })) {
100
+ results.set(r.id, r);
101
+ progress.onResult(r);
102
+ const percent = Math.floor(progress.fraction * 100);
103
+ // A TTY redraws one line; a log gets a line every 10%.
104
+ if (percent !== lastPercent && (tty || percent % 10 === 0)) {
105
+ lastPercent = percent;
106
+ const eta = progress.remainingSeconds;
107
+ const line = `${bar(progress.fraction)} ${String(percent).padStart(3)}%${eta ? ` (${eta}s remaining)` : ''}`;
108
+ process.stderr.write(tty ? `\r${line}\x1b[K` : `${line}\n`);
109
+ }
110
+ }
111
+ progress.finish();
112
+ process.stderr.write(tty ? `\r${bar(1)} 100%\x1b[K\n\n` : '\n');
113
+ const rows = [...results.values()].toSorted((a, b) => a.id.localeCompare(b.id));
114
+ if (argv.json) {
115
+ console.log(JSON.stringify({ device: info, results: rows }, null, 2));
116
+ }
117
+ else {
118
+ console.log(formatTable(rows, benchmarks));
119
+ }
120
+ if (argv.report) {
121
+ // A failed submission is the maintainer's problem, not the user's: it goes to Sentry, not the terminal.
122
+ const id = await report(argv.apiHost, info, rows).catch((error) => {
123
+ Sentry.captureException(error);
124
+ return null;
125
+ });
126
+ if (id)
127
+ console.log(`\nReported to ${chalk.underline(`${SITE}/benchmark/${id}`)}`);
128
+ }
129
+ },
130
+ });
131
+ //# sourceMappingURL=$default.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"$default.js","sourceRoot":"","sources":["../../src/commands/$default.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { BenchmarkResult, BenchmarkDefinition } from 'webgpu-bench';
2
+ /** `f16-*` -> /^f16-.*$/ ; `*` matches anything, `?` one character. */
3
+ export declare function globToRegExp(glob: string): RegExp;
4
+ /** 1.544e12, 'FLOP' -> '1.54 TFLOP/s' */
5
+ export declare function formatRate(value: number, unit: string): string;
6
+ /** One line per benchmark: id, then the headline rate — or, for a skipped/failed row, why. */
7
+ export declare function formatTable(results: BenchmarkResult[], defs: readonly BenchmarkDefinition[]): string;
8
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEzE,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMjD;AAID,yCAAyC;AACzC,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAO9D;AAED,8FAA8F;AAC9F,wBAAgB,WAAW,CAAC,OAAO,EAAE,eAAe,EAAE,EAAE,IAAI,EAAE,SAAS,mBAAmB,EAAE,GAAG,MAAM,CAcpG"}
package/dist/format.js ADDED
@@ -0,0 +1,35 @@
1
+ import chalk from 'chalk';
2
+ /** `f16-*` -> /^f16-.*$/ ; `*` matches anything, `?` one character. */
3
+ export function globToRegExp(glob) {
4
+ const escaped = glob
5
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
6
+ .replace(/\*/g, '.*')
7
+ .replace(/\?/g, '.');
8
+ return new RegExp(`^${escaped}$`);
9
+ }
10
+ const SI = ['', 'k', 'M', 'G', 'T', 'P'];
11
+ /** 1.544e12, 'FLOP' -> '1.54 TFLOP/s' */
12
+ export function formatRate(value, unit) {
13
+ let i = 0;
14
+ while (value >= 1000 && i < SI.length - 1) {
15
+ value /= 1000;
16
+ i++;
17
+ }
18
+ return `${value.toFixed(2)} ${SI[i]}${unit}/s`;
19
+ }
20
+ /** One line per benchmark: id, then the headline rate — or, for a skipped/failed row, why. */
21
+ export function formatTable(results, defs) {
22
+ const width = Math.max(...results.map((r) => r.id.length));
23
+ return results
24
+ .map((r) => {
25
+ const unit = defs.find((d) => d.id === r.id)?.metric.unit ?? '';
26
+ const value = r.status === 'ok' && r.metricValue !== undefined
27
+ ? chalk.green(formatRate(r.metricValue, unit))
28
+ : r.status === 'skipped'
29
+ ? chalk.yellow(`skipped: ${r.message ?? ''}`)
30
+ : chalk.red(`${r.status}: ${r.message ?? ''}`);
31
+ return `${chalk.cyan(r.id.padEnd(width))} ${value}`;
32
+ })
33
+ .join('\n');
34
+ }
35
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,uEAAuE;AACvE,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,OAAO,GAAG,IAAI;SACjB,OAAO,CAAC,mBAAmB,EAAE,MAAM,CAAC;SACpC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC;SACpB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACvB,OAAO,IAAI,MAAM,CAAC,IAAI,OAAO,GAAG,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAEzC,yCAAyC;AACzC,MAAM,UAAU,UAAU,CAAC,KAAa,EAAE,IAAY;IACpD,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,KAAK,IAAI,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1C,KAAK,IAAI,IAAI,CAAC;QACd,CAAC,EAAE,CAAC;IACN,CAAC;IACD,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC;AACjD,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,WAAW,CAAC,OAA0B,EAAE,IAAoC;IAC1F,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAC3D,OAAO,OAAO;SACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QAChE,MAAM,KAAK,GACT,CAAC,CAAC,MAAM,KAAK,IAAI,IAAI,CAAC,CAAC,WAAW,KAAK,SAAS;YAC9C,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;YAC9C,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS;gBACtB,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;gBAC7C,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC,CAAC;QACrD,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC;IACvD,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=instrument.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instrument.d.ts","sourceRoot":"","sources":["../src/instrument.ts"],"names":[],"mappings":""}
@@ -0,0 +1,9 @@
1
+ import { createRequire } from 'node:module';
2
+ import * as Sentry from '@sentry/node';
3
+ const { version } = createRequire(import.meta.url)('../package.json');
4
+ // Imported first from cli.ts so the SDK is initialized before anything else loads.
5
+ Sentry.init({
6
+ dsn: 'https://1ec32947fec6c989458384059a9133cf@o4508898407481344.ingest.us.sentry.io/4512070547734528',
7
+ release: `webgpu-bench-cli@${version}`,
8
+ });
9
+ //# sourceMappingURL=instrument.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instrument.js","sourceRoot":"","sources":["../src/instrument.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,MAAM,MAAM,cAAc,CAAC;AAEvC,MAAM,EAAE,OAAO,EAAE,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAwB,CAAC;AAE7F,mFAAmF;AACnF,MAAM,CAAC,IAAI,CAAC;IACV,GAAG,EAAE,iGAAiG;IACtG,OAAO,EAAE,oBAAoB,OAAO,EAAE;CACvC,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "webgpu-bench-cli",
3
+ "version": "0.8.0",
4
+ "description": "Run the webgpu-bench GPU benchmark suite from the command line, no browser needed.",
5
+ "keywords": [
6
+ "bandwidth",
7
+ "benchmark",
8
+ "cli",
9
+ "flops",
10
+ "gpu",
11
+ "webgpu"
12
+ ],
13
+ "homepage": "https://github.com/bhouston/webgpu-bench",
14
+ "bugs": {
15
+ "url": "https://github.com/bhouston/webgpu-bench/issues"
16
+ },
17
+ "license": "MIT",
18
+ "author": "Ben Houston (https://ben3d.ca)",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/bhouston/webgpu-bench.git",
22
+ "directory": "packages/cli"
23
+ },
24
+ "bin": {
25
+ "webgpu-bench": "./dist/cli.js"
26
+ },
27
+ "type": "module",
28
+ "scripts": {
29
+ "build": "tsc",
30
+ "dev": "tsc -w --preserveWatchOutput --noEmitOnError",
31
+ "start": "node dist/cli.js",
32
+ "test": "tsc && vitest run",
33
+ "test:coverage": "tsc && vitest run --coverage"
34
+ },
35
+ "dependencies": {
36
+ "@sentry/node": "^10.74.0",
37
+ "chalk": "^5.6.2",
38
+ "webgpu": "^0.6.0",
39
+ "webgpu-bench": "^0.8.0",
40
+ "yargs": "^18.0.0",
41
+ "yargs-file-commands": "^1.2.2"
42
+ },
43
+ "devDependencies": {
44
+ "@types/yargs": "^17.0.35",
45
+ "@vitest/coverage-v8": "5.0.0",
46
+ "@webgpu/types": "^0.1.69",
47
+ "vitest-command-line": "^0.9.0"
48
+ },
49
+ "engines": {
50
+ "node": ">=22.0.0"
51
+ }
52
+ }