webgpu-bench 0.1.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 +21 -0
- package/README.md +120 -0
- package/dist/benchmarks/common.d.ts +71 -0
- package/dist/benchmarks/common.d.ts.map +1 -0
- package/dist/benchmarks/common.js +104 -0
- package/dist/benchmarks/common.js.map +1 -0
- package/dist/benchmarks/flopsCommon.d.ts +52 -0
- package/dist/benchmarks/flopsCommon.d.ts.map +1 -0
- package/dist/benchmarks/flopsCommon.js +78 -0
- package/dist/benchmarks/flopsCommon.js.map +1 -0
- package/dist/benchmarks/flopsConvert.d.ts +18 -0
- package/dist/benchmarks/flopsConvert.d.ts.map +1 -0
- package/dist/benchmarks/flopsConvert.js +61 -0
- package/dist/benchmarks/flopsConvert.js.map +1 -0
- package/dist/benchmarks/flopsF16.d.ts +12 -0
- package/dist/benchmarks/flopsF16.d.ts.map +1 -0
- package/dist/benchmarks/flopsF16.js +54 -0
- package/dist/benchmarks/flopsF16.js.map +1 -0
- package/dist/benchmarks/flopsF32.d.ts +12 -0
- package/dist/benchmarks/flopsF32.d.ts.map +1 -0
- package/dist/benchmarks/flopsF32.js +50 -0
- package/dist/benchmarks/flopsF32.js.map +1 -0
- package/dist/benchmarks/flopsI8.d.ts +16 -0
- package/dist/benchmarks/flopsI8.d.ts.map +1 -0
- package/dist/benchmarks/flopsI8.js +83 -0
- package/dist/benchmarks/flopsI8.js.map +1 -0
- package/dist/benchmarks/flopsMath.d.ts +28 -0
- package/dist/benchmarks/flopsMath.d.ts.map +1 -0
- package/dist/benchmarks/flopsMath.js +98 -0
- package/dist/benchmarks/flopsMath.js.map +1 -0
- package/dist/benchmarks/streamBandwidth.d.ts +17 -0
- package/dist/benchmarks/streamBandwidth.d.ts.map +1 -0
- package/dist/benchmarks/streamBandwidth.js +78 -0
- package/dist/benchmarks/streamBandwidth.js.map +1 -0
- package/dist/data/generate.d.ts +23 -0
- package/dist/data/generate.d.ts.map +1 -0
- package/dist/data/generate.js +41 -0
- package/dist/data/generate.js.map +1 -0
- package/dist/gpu/benchmarkRunner.d.ts +98 -0
- package/dist/gpu/benchmarkRunner.d.ts.map +1 -0
- package/dist/gpu/benchmarkRunner.js +178 -0
- package/dist/gpu/benchmarkRunner.js.map +1 -0
- package/dist/gpu/buffers.d.ts +8 -0
- package/dist/gpu/buffers.d.ts.map +1 -0
- package/dist/gpu/buffers.js +41 -0
- package/dist/gpu/buffers.js.map +1 -0
- package/dist/gpu/context.d.ts +16 -0
- package/dist/gpu/context.d.ts.map +1 -0
- package/dist/gpu/context.js +71 -0
- package/dist/gpu/context.js.map +1 -0
- package/dist/gpu/timing.d.ts +20 -0
- package/dist/gpu/timing.d.ts.map +1 -0
- package/dist/gpu/timing.js +55 -0
- package/dist/gpu/timing.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/sampling.d.ts +96 -0
- package/dist/sampling.d.ts.map +1 -0
- package/dist/sampling.js +239 -0
- package/dist/sampling.js.map +1 -0
- package/dist/shaders/flopsF16Mat4.d.ts +7 -0
- package/dist/shaders/flopsF16Mat4.d.ts.map +1 -0
- package/dist/shaders/flopsF16Mat4.js +40 -0
- package/dist/shaders/flopsF16Mat4.js.map +1 -0
- package/dist/shaders/flopsF16Matvec.d.ts +14 -0
- package/dist/shaders/flopsF16Matvec.d.ts.map +1 -0
- package/dist/shaders/flopsF16Matvec.js +58 -0
- package/dist/shaders/flopsF16Matvec.js.map +1 -0
- package/dist/shaders/flopsF16Scalar.d.ts +8 -0
- package/dist/shaders/flopsF16Scalar.d.ts.map +1 -0
- package/dist/shaders/flopsF16Scalar.js +71 -0
- package/dist/shaders/flopsF16Scalar.js.map +1 -0
- package/dist/shaders/flopsF16Vec4.d.ts +7 -0
- package/dist/shaders/flopsF16Vec4.d.ts.map +1 -0
- package/dist/shaders/flopsF16Vec4.js +35 -0
- package/dist/shaders/flopsF16Vec4.js.map +1 -0
- package/dist/shaders/flopsF32Div.d.ts +10 -0
- package/dist/shaders/flopsF32Div.d.ts.map +1 -0
- package/dist/shaders/flopsF32Div.js +71 -0
- package/dist/shaders/flopsF32Div.js.map +1 -0
- package/dist/shaders/flopsF32F16Convert.d.ts +11 -0
- package/dist/shaders/flopsF32F16Convert.d.ts.map +1 -0
- package/dist/shaders/flopsF32F16Convert.js +73 -0
- package/dist/shaders/flopsF32F16Convert.js.map +1 -0
- package/dist/shaders/flopsF32Log.d.ts +11 -0
- package/dist/shaders/flopsF32Log.d.ts.map +1 -0
- package/dist/shaders/flopsF32Log.js +71 -0
- package/dist/shaders/flopsF32Log.js.map +1 -0
- package/dist/shaders/flopsF32Mat4.d.ts +9 -0
- package/dist/shaders/flopsF32Mat4.d.ts.map +1 -0
- package/dist/shaders/flopsF32Mat4.js +38 -0
- package/dist/shaders/flopsF32Mat4.js.map +1 -0
- package/dist/shaders/flopsF32Matvec.d.ts +14 -0
- package/dist/shaders/flopsF32Matvec.d.ts.map +1 -0
- package/dist/shaders/flopsF32Matvec.js +58 -0
- package/dist/shaders/flopsF32Matvec.js.map +1 -0
- package/dist/shaders/flopsF32Pow.d.ts +10 -0
- package/dist/shaders/flopsF32Pow.d.ts.map +1 -0
- package/dist/shaders/flopsF32Pow.js +71 -0
- package/dist/shaders/flopsF32Pow.js.map +1 -0
- package/dist/shaders/flopsF32Rsqrt.d.ts +12 -0
- package/dist/shaders/flopsF32Rsqrt.d.ts.map +1 -0
- package/dist/shaders/flopsF32Rsqrt.js +72 -0
- package/dist/shaders/flopsF32Rsqrt.js.map +1 -0
- package/dist/shaders/flopsF32Scalar.d.ts +11 -0
- package/dist/shaders/flopsF32Scalar.d.ts.map +1 -0
- package/dist/shaders/flopsF32Scalar.js +72 -0
- package/dist/shaders/flopsF32Scalar.js.map +1 -0
- package/dist/shaders/flopsF32Sincos.d.ts +9 -0
- package/dist/shaders/flopsF32Sincos.d.ts.map +1 -0
- package/dist/shaders/flopsF32Sincos.js +68 -0
- package/dist/shaders/flopsF32Sincos.js.map +1 -0
- package/dist/shaders/flopsF32Sqrt.d.ts +9 -0
- package/dist/shaders/flopsF32Sqrt.d.ts.map +1 -0
- package/dist/shaders/flopsF32Sqrt.js +69 -0
- package/dist/shaders/flopsF32Sqrt.js.map +1 -0
- package/dist/shaders/flopsF32Vec4.d.ts +9 -0
- package/dist/shaders/flopsF32Vec4.d.ts.map +1 -0
- package/dist/shaders/flopsF32Vec4.js +33 -0
- package/dist/shaders/flopsF32Vec4.js.map +1 -0
- package/dist/shaders/flopsI32Div.d.ts +10 -0
- package/dist/shaders/flopsI32Div.d.ts.map +1 -0
- package/dist/shaders/flopsI32Div.js +71 -0
- package/dist/shaders/flopsI32Div.js.map +1 -0
- package/dist/shaders/flopsI32F16Convert.d.ts +10 -0
- package/dist/shaders/flopsI32F16Convert.d.ts.map +1 -0
- package/dist/shaders/flopsI32F16Convert.js +71 -0
- package/dist/shaders/flopsI32F16Convert.js.map +1 -0
- package/dist/shaders/flopsI32F32Convert.d.ts +10 -0
- package/dist/shaders/flopsI32F32Convert.d.ts.map +1 -0
- package/dist/shaders/flopsI32F32Convert.js +71 -0
- package/dist/shaders/flopsI32F32Convert.js.map +1 -0
- package/dist/shaders/flopsI8Dp4a.d.ts +23 -0
- package/dist/shaders/flopsI8Dp4a.d.ts.map +1 -0
- package/dist/shaders/flopsI8Dp4a.js +49 -0
- package/dist/shaders/flopsI8Dp4a.js.map +1 -0
- package/dist/shaders/flopsI8Mat4.d.ts +9 -0
- package/dist/shaders/flopsI8Mat4.d.ts.map +1 -0
- package/dist/shaders/flopsI8Mat4.js +41 -0
- package/dist/shaders/flopsI8Mat4.js.map +1 -0
- package/dist/shaders/flopsI8Matvec.d.ts +16 -0
- package/dist/shaders/flopsI8Matvec.d.ts.map +1 -0
- package/dist/shaders/flopsI8Matvec.js +57 -0
- package/dist/shaders/flopsI8Matvec.js.map +1 -0
- package/dist/shaders/flopsI8MatvecDp4a.d.ts +17 -0
- package/dist/shaders/flopsI8MatvecDp4a.d.ts.map +1 -0
- package/dist/shaders/flopsI8MatvecDp4a.js +60 -0
- package/dist/shaders/flopsI8MatvecDp4a.js.map +1 -0
- package/dist/shaders/flopsI8Scalar.d.ts +9 -0
- package/dist/shaders/flopsI8Scalar.d.ts.map +1 -0
- package/dist/shaders/flopsI8Scalar.js +70 -0
- package/dist/shaders/flopsI8Scalar.js.map +1 -0
- package/dist/shaders/flopsI8Vec4.d.ts +7 -0
- package/dist/shaders/flopsI8Vec4.d.ts.map +1 -0
- package/dist/shaders/flopsI8Vec4.js +31 -0
- package/dist/shaders/flopsI8Vec4.js.map +1 -0
- package/dist/shaders/flopsU32PackUnpack.d.ts +11 -0
- package/dist/shaders/flopsU32PackUnpack.d.ts.map +1 -0
- package/dist/shaders/flopsU32PackUnpack.js +55 -0
- package/dist/shaders/flopsU32PackUnpack.js.map +1 -0
- package/dist/shaders/streamRead.d.ts +10 -0
- package/dist/shaders/streamRead.d.ts.map +1 -0
- package/dist/shaders/streamRead.js +33 -0
- package/dist/shaders/streamRead.js.map +1 -0
- package/dist/shaders/streamWrite.d.ts +8 -0
- package/dist/shaders/streamWrite.d.ts.map +1 -0
- package/dist/shaders/streamWrite.js +29 -0
- package/dist/shaders/streamWrite.js.map +1 -0
- package/dist/stats.d.ts +31 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +67 -0
- package/dist/stats.js.map +1 -0
- package/dist/suite.d.ts +25 -0
- package/dist/suite.d.ts.map +1 -0
- package/dist/suite.js +196 -0
- package/dist/suite.js.map +1 -0
- package/dist/types.d.ts +171 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { SamplingStopReason, Stats, SuiteProgressEvent } from './types.ts';
|
|
2
|
+
/** Knobs for the round-robin, best-of-N sampling scheduler. */
|
|
3
|
+
export interface SamplingConfig {
|
|
4
|
+
/** Fewest kept measurements per benchmark before it can converge. Default 3. */
|
|
5
|
+
minRounds?: number;
|
|
6
|
+
/** Most kept measurements per benchmark. Default 10. */
|
|
7
|
+
maxRounds?: number;
|
|
8
|
+
/** Converged once the best run hasn't improved (by more than `improvementTolerance`) over this many kept measurements. Default 2. */
|
|
9
|
+
stableRounds?: number;
|
|
10
|
+
/** Relative improvement that counts as "still improving". Default 0.01. */
|
|
11
|
+
improvementTolerance?: number;
|
|
12
|
+
/** A measurement this fraction slower than the best is throttled and discarded. Default 0.20. */
|
|
13
|
+
throttleThreshold?: number;
|
|
14
|
+
/** Idle gap between consecutive measurements, in ms. Default 100. */
|
|
15
|
+
idleMs?: number;
|
|
16
|
+
/** Suite-wide pause when throttling is detected, in ms. Default 3000. */
|
|
17
|
+
cooldownMs?: number;
|
|
18
|
+
/** Cooldown pauses before giving up on still-unconverged benchmarks. Default 3. */
|
|
19
|
+
maxCooldowns?: number;
|
|
20
|
+
/** Fraction of a round's measurements (and at least two) that must be throttled to trigger a cooldown. Default 0.5. */
|
|
21
|
+
throttledFraction?: number;
|
|
22
|
+
}
|
|
23
|
+
export declare const DEFAULT_SAMPLING: Required<SamplingConfig>;
|
|
24
|
+
/** What the scheduler needs from a benchmark: something it can measure repeatedly. */
|
|
25
|
+
export interface Sampleable {
|
|
26
|
+
id: string;
|
|
27
|
+
/** One-time setup (calibration, warmups). Called before the first `sample()`. */
|
|
28
|
+
calibrate(): Promise<void>;
|
|
29
|
+
/** One timed measurement: per-op time in ms. */
|
|
30
|
+
sample(): Promise<number>;
|
|
31
|
+
}
|
|
32
|
+
/** Running state of one benchmark inside the scheduler; surfaced after every measurement via `onUpdate`. */
|
|
33
|
+
export interface SampleState {
|
|
34
|
+
id: string;
|
|
35
|
+
/** Kept measurements, in order taken. */
|
|
36
|
+
timesMs: number[];
|
|
37
|
+
/** Measurements discarded as throttled, in order taken. */
|
|
38
|
+
throttledMs: number[];
|
|
39
|
+
/** Best kept measurement so far; Infinity before the first. */
|
|
40
|
+
bestMs: number;
|
|
41
|
+
stopReason?: SamplingStopReason;
|
|
42
|
+
/** Set if calibration or a measurement threw; the benchmark is retired. */
|
|
43
|
+
error?: unknown;
|
|
44
|
+
}
|
|
45
|
+
export interface SampleStateSnapshot extends SampleState {
|
|
46
|
+
stats?: Stats;
|
|
47
|
+
}
|
|
48
|
+
export interface RunSamplingOptions extends SamplingConfig {
|
|
49
|
+
/** Called after every measurement (and when a benchmark is retired) with that benchmark's state. */
|
|
50
|
+
onUpdate?: (state: SampleStateSnapshot) => void | Promise<void>;
|
|
51
|
+
onProgress?: (event: SuiteProgressEvent) => void;
|
|
52
|
+
/** Injectable for tests; defaults to a real `setTimeout` sleep. */
|
|
53
|
+
sleep?: (ms: number) => Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Classifies one measurement against the benchmark's best-so-far and folds it
|
|
57
|
+
* into the state. Pure apart from mutating `state`.
|
|
58
|
+
*
|
|
59
|
+
* Returns whether the sample was kept. A throttled sample (more than
|
|
60
|
+
* `throttleThreshold` slower than the best) is recorded in `throttledMs` and
|
|
61
|
+
* doesn't count toward convergence — it says something about the device's
|
|
62
|
+
* temperature, not the kernel.
|
|
63
|
+
*/
|
|
64
|
+
export declare function recordSample(state: SampleState, ms: number, cfg: Required<SamplingConfig>): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Whether the measurements taken in one round add up to "the device is
|
|
67
|
+
* throttled". Thermal throttling slows every kernel, so the signal is
|
|
68
|
+
* agreement across benchmarks: at least two of them, and at least
|
|
69
|
+
* `throttledFraction` of the round. One benchmark repeatedly coming in slow
|
|
70
|
+
* while the rest are fine is that benchmark's own noise (Safari's
|
|
71
|
+
* wall-clock timing of the write-bandwidth kernel jitters 15–40%), not
|
|
72
|
+
* heat — pausing the suite for it would just waste time.
|
|
73
|
+
*/
|
|
74
|
+
export declare function roundIsThrottled(roundThrottled: readonly boolean[], cfg: Required<SamplingConfig>): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Round-robin, best-of-N sampling across many benchmarks.
|
|
77
|
+
*
|
|
78
|
+
* Rather than running each benchmark to convergence before starting the
|
|
79
|
+
* next (which heats a phone up in seconds and leaves every later benchmark
|
|
80
|
+
* measuring a throttled GPU), every round takes one short measurement of
|
|
81
|
+
* every still-active benchmark, with an idle gap between measurements so
|
|
82
|
+
* the GPU duty-cycles. Benchmarks retire as soon as their best run has
|
|
83
|
+
* stopped improving (`isBestStable`). Measurements that come in well slower
|
|
84
|
+
* than a benchmark's best are discarded as throttled; when a round shows
|
|
85
|
+
* the device throttled overall — half the benchmarks slow, or any one slow
|
|
86
|
+
* twice running — the whole suite pauses for `cooldownMs` and tries again,
|
|
87
|
+
* up to `maxCooldowns` times, after which whatever is still unconverged is
|
|
88
|
+
* reported with `stopReason: 'throttled'` alongside its best run so far.
|
|
89
|
+
*
|
|
90
|
+
* Only the best run is the headline number: every noise source makes runs
|
|
91
|
+
* slower, never faster, so the minimum is the most robust estimate of what
|
|
92
|
+
* the device can do.
|
|
93
|
+
*/
|
|
94
|
+
export declare function runSampling(benchmarks: readonly Sampleable[], options?: RunSamplingOptions): Promise<Map<string, SampleStateSnapshot>>;
|
|
95
|
+
export declare function resolveSamplingConfig(cfg: SamplingConfig): Required<SamplingConfig>;
|
|
96
|
+
//# sourceMappingURL=sampling.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sampling.d.ts","sourceRoot":"","sources":["../src/sampling.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEhF,+DAA+D;AAC/D,MAAM,WAAW,cAAc;IAC7B,gFAAgF;IAChF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qIAAqI;IACrI,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,iGAAiG;IACjG,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mFAAmF;IACnF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uHAAuH;IACvH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,cAAc,CAUrD,CAAC;AAEF,sFAAsF;AACtF,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,iFAAiF;IACjF,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,gDAAgD;IAChD,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CAC3B;AAED,4GAA4G;AAC5G,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,yCAAyC;IACzC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,2DAA2D;IAC3D,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,kBAAkB,CAAC;IAChC,2EAA2E;IAC3E,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,mBAAoB,SAAQ,WAAW;IACtD,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED,MAAM,WAAW,kBAAmB,SAAQ,cAAc;IACxD,oGAAoG;IACpG,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACjD,mEAAmE;IACnE,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAmDD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,CAAC,cAAc,CAAC,GAAG,OAAO,CAsBnG;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,cAAc,EAAE,SAAS,OAAO,EAAE,EAAE,GAAG,EAAE,QAAQ,CAAC,cAAc,CAAC,GAAG,OAAO,CAG3G;AAoBD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,WAAW,CAC/B,UAAU,EAAE,SAAS,UAAU,EAAE,EACjC,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAqF3C;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,cAAc,GAAG,QAAQ,CAAC,cAAc,CAAC,CAanF"}
|
package/dist/sampling.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { computeStats, isBestStable, isThrottled } from "./stats.js";
|
|
2
|
+
export const DEFAULT_SAMPLING = {
|
|
3
|
+
minRounds: 3,
|
|
4
|
+
maxRounds: 10,
|
|
5
|
+
stableRounds: 2,
|
|
6
|
+
improvementTolerance: 0.01,
|
|
7
|
+
throttleThreshold: 0.2,
|
|
8
|
+
idleMs: 100,
|
|
9
|
+
cooldownMs: 3000,
|
|
10
|
+
maxCooldowns: 3,
|
|
11
|
+
throttledFraction: 0.5,
|
|
12
|
+
};
|
|
13
|
+
const isActive = (s) => s.stopReason === undefined && s.error === undefined;
|
|
14
|
+
/**
|
|
15
|
+
* The idle gap doubles as the UI's render window: after the timer, wait for
|
|
16
|
+
* the main thread to go idle (table re-render committed, its paint queued)
|
|
17
|
+
* so the compositor's GPU work lands here rather than inside the next
|
|
18
|
+
* measurement. Falls back to a plain timer where `requestIdleCallback` is
|
|
19
|
+
* missing; the timeout bounds the wait on a busy page.
|
|
20
|
+
*/
|
|
21
|
+
const realSleep = (ms) => new Promise((resolve) => setTimeout(() => {
|
|
22
|
+
if (typeof requestIdleCallback === 'function')
|
|
23
|
+
requestIdleCallback(() => resolve(), { timeout: 1000 });
|
|
24
|
+
else
|
|
25
|
+
resolve();
|
|
26
|
+
}, ms));
|
|
27
|
+
/**
|
|
28
|
+
* A backgrounded tab gets a throttled event loop and a lower-priority GPU
|
|
29
|
+
* queue, so nothing measured while hidden is a property of the device.
|
|
30
|
+
* `waitVisible` blocks until the page is showing; `hiddenSince` reports
|
|
31
|
+
* whether it went hidden at any point after the last `mark()`.
|
|
32
|
+
*/
|
|
33
|
+
function visibilityGuard() {
|
|
34
|
+
const doc = typeof document === 'undefined' ? undefined : document;
|
|
35
|
+
let hidden = false;
|
|
36
|
+
const onChange = () => {
|
|
37
|
+
if (doc?.hidden)
|
|
38
|
+
hidden = true;
|
|
39
|
+
};
|
|
40
|
+
doc?.addEventListener('visibilitychange', onChange);
|
|
41
|
+
return {
|
|
42
|
+
mark: () => {
|
|
43
|
+
hidden = Boolean(doc?.hidden);
|
|
44
|
+
},
|
|
45
|
+
hiddenSince: () => hidden || Boolean(doc?.hidden),
|
|
46
|
+
waitVisible: () => new Promise((resolve) => {
|
|
47
|
+
if (!doc?.hidden)
|
|
48
|
+
return resolve();
|
|
49
|
+
const onVisible = () => {
|
|
50
|
+
if (doc.hidden)
|
|
51
|
+
return;
|
|
52
|
+
doc.removeEventListener('visibilitychange', onVisible);
|
|
53
|
+
resolve();
|
|
54
|
+
};
|
|
55
|
+
doc.addEventListener('visibilitychange', onVisible);
|
|
56
|
+
}),
|
|
57
|
+
dispose: () => doc?.removeEventListener('visibilitychange', onChange),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Classifies one measurement against the benchmark's best-so-far and folds it
|
|
62
|
+
* into the state. Pure apart from mutating `state`.
|
|
63
|
+
*
|
|
64
|
+
* Returns whether the sample was kept. A throttled sample (more than
|
|
65
|
+
* `throttleThreshold` slower than the best) is recorded in `throttledMs` and
|
|
66
|
+
* doesn't count toward convergence — it says something about the device's
|
|
67
|
+
* temperature, not the kernel.
|
|
68
|
+
*/
|
|
69
|
+
export function recordSample(state, ms, cfg) {
|
|
70
|
+
if (isThrottled(ms, state.bestMs, cfg.throttleThreshold)) {
|
|
71
|
+
state.throttledMs.push(ms);
|
|
72
|
+
// A benchmark that keeps coming back throttled while the rest of the
|
|
73
|
+
// suite is fine (so no cooldown fires) must still terminate.
|
|
74
|
+
if (state.timesMs.length + state.throttledMs.length >= 2 * cfg.maxRounds)
|
|
75
|
+
state.stopReason = 'max-rounds';
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
state.timesMs.push(ms);
|
|
79
|
+
if (ms < state.bestMs)
|
|
80
|
+
state.bestMs = ms;
|
|
81
|
+
if (isBestStable(state.timesMs, {
|
|
82
|
+
minRuns: cfg.minRounds,
|
|
83
|
+
stableRuns: cfg.stableRounds,
|
|
84
|
+
tolerance: cfg.improvementTolerance,
|
|
85
|
+
})) {
|
|
86
|
+
state.stopReason = 'converged';
|
|
87
|
+
}
|
|
88
|
+
else if (state.timesMs.length >= cfg.maxRounds) {
|
|
89
|
+
state.stopReason = 'max-rounds';
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Whether the measurements taken in one round add up to "the device is
|
|
95
|
+
* throttled". Thermal throttling slows every kernel, so the signal is
|
|
96
|
+
* agreement across benchmarks: at least two of them, and at least
|
|
97
|
+
* `throttledFraction` of the round. One benchmark repeatedly coming in slow
|
|
98
|
+
* while the rest are fine is that benchmark's own noise (Safari's
|
|
99
|
+
* wall-clock timing of the write-bandwidth kernel jitters 15–40%), not
|
|
100
|
+
* heat — pausing the suite for it would just waste time.
|
|
101
|
+
*/
|
|
102
|
+
export function roundIsThrottled(roundThrottled, cfg) {
|
|
103
|
+
const throttledCount = roundThrottled.filter(Boolean).length;
|
|
104
|
+
return throttledCount >= 2 && throttledCount / roundThrottled.length >= cfg.throttledFraction;
|
|
105
|
+
}
|
|
106
|
+
/** In-place Fisher-Yates. */
|
|
107
|
+
function shuffle(items) {
|
|
108
|
+
for (let i = items.length - 1; i > 0; i--) {
|
|
109
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
110
|
+
[items[i], items[j]] = [items[j], items[i]];
|
|
111
|
+
}
|
|
112
|
+
return items;
|
|
113
|
+
}
|
|
114
|
+
function snapshot(state) {
|
|
115
|
+
return {
|
|
116
|
+
...state,
|
|
117
|
+
timesMs: [...state.timesMs],
|
|
118
|
+
throttledMs: [...state.throttledMs],
|
|
119
|
+
stats: state.timesMs.length > 0 ? computeStats(state.timesMs) : undefined,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Round-robin, best-of-N sampling across many benchmarks.
|
|
124
|
+
*
|
|
125
|
+
* Rather than running each benchmark to convergence before starting the
|
|
126
|
+
* next (which heats a phone up in seconds and leaves every later benchmark
|
|
127
|
+
* measuring a throttled GPU), every round takes one short measurement of
|
|
128
|
+
* every still-active benchmark, with an idle gap between measurements so
|
|
129
|
+
* the GPU duty-cycles. Benchmarks retire as soon as their best run has
|
|
130
|
+
* stopped improving (`isBestStable`). Measurements that come in well slower
|
|
131
|
+
* than a benchmark's best are discarded as throttled; when a round shows
|
|
132
|
+
* the device throttled overall — half the benchmarks slow, or any one slow
|
|
133
|
+
* twice running — the whole suite pauses for `cooldownMs` and tries again,
|
|
134
|
+
* up to `maxCooldowns` times, after which whatever is still unconverged is
|
|
135
|
+
* reported with `stopReason: 'throttled'` alongside its best run so far.
|
|
136
|
+
*
|
|
137
|
+
* Only the best run is the headline number: every noise source makes runs
|
|
138
|
+
* slower, never faster, so the minimum is the most robust estimate of what
|
|
139
|
+
* the device can do.
|
|
140
|
+
*/
|
|
141
|
+
export async function runSampling(benchmarks, options = {}) {
|
|
142
|
+
const cfg = resolveSamplingConfig(options);
|
|
143
|
+
const sleep = options.sleep ?? realSleep;
|
|
144
|
+
const onUpdate = options.onUpdate ?? (() => { });
|
|
145
|
+
const onProgress = options.onProgress ?? (() => { });
|
|
146
|
+
const states = new Map();
|
|
147
|
+
const samplers = new Map();
|
|
148
|
+
for (const b of benchmarks) {
|
|
149
|
+
states.set(b.id, { id: b.id, timesMs: [], throttledMs: [], bestMs: Number.POSITIVE_INFINITY });
|
|
150
|
+
samplers.set(b.id, b);
|
|
151
|
+
}
|
|
152
|
+
const visibility = visibilityGuard();
|
|
153
|
+
let cooldowns = 0;
|
|
154
|
+
let calibrated = false;
|
|
155
|
+
try {
|
|
156
|
+
for (let round = 1;; round++) {
|
|
157
|
+
// Fresh order every round: a fixed order would hand the same kernels
|
|
158
|
+
// the cool GPU every time and leave the rest measuring a warm one.
|
|
159
|
+
const active = shuffle([...states.values()].filter(isActive));
|
|
160
|
+
if (active.length === 0)
|
|
161
|
+
break;
|
|
162
|
+
onProgress({ type: 'round', round, active: active.length });
|
|
163
|
+
const roundStates = [];
|
|
164
|
+
const roundThrottled = [];
|
|
165
|
+
let first = true;
|
|
166
|
+
for (const state of active) {
|
|
167
|
+
const sampler = samplers.get(state.id);
|
|
168
|
+
try {
|
|
169
|
+
if (!first && cfg.idleMs > 0)
|
|
170
|
+
await sleep(cfg.idleMs);
|
|
171
|
+
first = false;
|
|
172
|
+
await visibility.waitVisible();
|
|
173
|
+
visibility.mark();
|
|
174
|
+
if (!calibrated)
|
|
175
|
+
await sampler.calibrate();
|
|
176
|
+
const ms = await sampler.sample();
|
|
177
|
+
// Hidden at any point during the measurement: not a device number. Drop it and retry next round.
|
|
178
|
+
if (visibility.hiddenSince())
|
|
179
|
+
continue;
|
|
180
|
+
if (!Number.isFinite(ms) || ms <= 0) {
|
|
181
|
+
throw new Error(`measured a per-op time of ${ms}ms — the GPU pass likely did no work (check for an 'uncapturederror' in the console).`);
|
|
182
|
+
}
|
|
183
|
+
const kept = recordSample(state, ms, cfg);
|
|
184
|
+
roundStates.push(state);
|
|
185
|
+
roundThrottled.push(!kept);
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
state.error = error;
|
|
189
|
+
}
|
|
190
|
+
await onUpdate(snapshot(state));
|
|
191
|
+
}
|
|
192
|
+
calibrated = true;
|
|
193
|
+
if (roundStates.length > 0 && roundIsThrottled(roundThrottled, cfg)) {
|
|
194
|
+
const throttledIds = roundStates.filter((_, i) => roundThrottled[i]).map((s) => s.id);
|
|
195
|
+
if (cooldowns >= cfg.maxCooldowns) {
|
|
196
|
+
onProgress({ type: 'throttle-abort', throttledIds });
|
|
197
|
+
for (const s of states.values()) {
|
|
198
|
+
if (isActive(s)) {
|
|
199
|
+
s.stopReason = s.timesMs.length > 0 ? 'throttled' : undefined;
|
|
200
|
+
if (s.timesMs.length === 0) {
|
|
201
|
+
s.error = new Error('Every measurement was discarded as throttled and the cooldown budget is exhausted.');
|
|
202
|
+
}
|
|
203
|
+
await onUpdate(snapshot(s));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
cooldowns += 1;
|
|
209
|
+
onProgress({
|
|
210
|
+
type: 'cooldown',
|
|
211
|
+
attempt: cooldowns,
|
|
212
|
+
maxAttempts: cfg.maxCooldowns,
|
|
213
|
+
ms: cfg.cooldownMs,
|
|
214
|
+
throttledIds,
|
|
215
|
+
});
|
|
216
|
+
await sleep(cfg.cooldownMs);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
finally {
|
|
221
|
+
visibility.dispose();
|
|
222
|
+
}
|
|
223
|
+
return new Map([...states].map(([id, s]) => [id, snapshot(s)]));
|
|
224
|
+
}
|
|
225
|
+
export function resolveSamplingConfig(cfg) {
|
|
226
|
+
const minRounds = Math.max(1, cfg.minRounds ?? DEFAULT_SAMPLING.minRounds);
|
|
227
|
+
return {
|
|
228
|
+
minRounds,
|
|
229
|
+
maxRounds: Math.max(minRounds, cfg.maxRounds ?? DEFAULT_SAMPLING.maxRounds),
|
|
230
|
+
stableRounds: Math.max(1, cfg.stableRounds ?? DEFAULT_SAMPLING.stableRounds),
|
|
231
|
+
improvementTolerance: cfg.improvementTolerance ?? DEFAULT_SAMPLING.improvementTolerance,
|
|
232
|
+
throttleThreshold: cfg.throttleThreshold ?? DEFAULT_SAMPLING.throttleThreshold,
|
|
233
|
+
idleMs: cfg.idleMs ?? DEFAULT_SAMPLING.idleMs,
|
|
234
|
+
cooldownMs: cfg.cooldownMs ?? DEFAULT_SAMPLING.cooldownMs,
|
|
235
|
+
maxCooldowns: cfg.maxCooldowns ?? DEFAULT_SAMPLING.maxCooldowns,
|
|
236
|
+
throttledFraction: cfg.throttledFraction ?? DEFAULT_SAMPLING.throttledFraction,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=sampling.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sampling.js","sourceRoot":"","sources":["../src/sampling.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fp16 mat4 FLOPS probe — {@link flopsF32Mat4Wgsl}'s bounded
|
|
3
|
+
* `x = m * x + c` recurrence, but `m`, `c`, and `x` are all `f16`, so the
|
|
4
|
+
* mat4x4 * vec4 multiply runs entirely in half precision.
|
|
5
|
+
*/
|
|
6
|
+
export declare const flopsF16Mat4Wgsl = "\nenable f16;\n\nstruct Params {\n threads: u32,\n iterations: u32,\n};\n\n@group(0) @binding(0) var<uniform> params: Params;\n@group(0) @binding(1) var<storage, read_write> out: array<f32>;\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3<u32>) {\n let idx = gid.x;\n if (idx >= params.threads) {\n return;\n }\n let s: f16 = f16(idx & 15u) * f16(0.001) + f16(0.001);\n let one: f16 = f16(1.0);\n let three_s: f16 = f16(3.0) * s;\n let m: mat4x4<f16> = mat4x4<f16>(\n vec4<f16>(one - three_s, s, s, s),\n vec4<f16>(s, one - three_s, s, s),\n vec4<f16>(s, s, one - three_s, s),\n vec4<f16>(s, s, s, one - three_s),\n );\n let c: vec4<f16> = vec4<f16>(s, s, s, s);\n var x: vec4<f16> = vec4<f16>(f16(idx & 255u) * f16(0.0001));\n for (var i: u32 = 0u; i < params.iterations; i = i + 1u) {\n x = m * x + c;\n }\n out[idx] = f32(x.x + x.y + x.z + x.w);\n}\n";
|
|
7
|
+
//# sourceMappingURL=flopsF16Mat4.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flopsF16Mat4.d.ts","sourceRoot":"","sources":["../../src/shaders/flopsF16Mat4.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,65BAiC5B,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fp16 mat4 FLOPS probe — {@link flopsF32Mat4Wgsl}'s bounded
|
|
3
|
+
* `x = m * x + c` recurrence, but `m`, `c`, and `x` are all `f16`, so the
|
|
4
|
+
* mat4x4 * vec4 multiply runs entirely in half precision.
|
|
5
|
+
*/
|
|
6
|
+
export const flopsF16Mat4Wgsl = /* wgsl */ `
|
|
7
|
+
enable f16;
|
|
8
|
+
|
|
9
|
+
struct Params {
|
|
10
|
+
threads: u32,
|
|
11
|
+
iterations: u32,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
@group(0) @binding(0) var<uniform> params: Params;
|
|
15
|
+
@group(0) @binding(1) var<storage, read_write> out: array<f32>;
|
|
16
|
+
|
|
17
|
+
@compute @workgroup_size(64)
|
|
18
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
19
|
+
let idx = gid.x;
|
|
20
|
+
if (idx >= params.threads) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
let s: f16 = f16(idx & 15u) * f16(0.001) + f16(0.001);
|
|
24
|
+
let one: f16 = f16(1.0);
|
|
25
|
+
let three_s: f16 = f16(3.0) * s;
|
|
26
|
+
let m: mat4x4<f16> = mat4x4<f16>(
|
|
27
|
+
vec4<f16>(one - three_s, s, s, s),
|
|
28
|
+
vec4<f16>(s, one - three_s, s, s),
|
|
29
|
+
vec4<f16>(s, s, one - three_s, s),
|
|
30
|
+
vec4<f16>(s, s, s, one - three_s),
|
|
31
|
+
);
|
|
32
|
+
let c: vec4<f16> = vec4<f16>(s, s, s, s);
|
|
33
|
+
var x: vec4<f16> = vec4<f16>(f16(idx & 255u) * f16(0.0001));
|
|
34
|
+
for (var i: u32 = 0u; i < params.iterations; i = i + 1u) {
|
|
35
|
+
x = m * x + c;
|
|
36
|
+
}
|
|
37
|
+
out[idx] = f32(x.x + x.y + x.z + x.w);
|
|
38
|
+
}
|
|
39
|
+
`;
|
|
40
|
+
//# sourceMappingURL=flopsF16Mat4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flopsF16Mat4.js","sourceRoot":"","sources":["../../src/shaders/flopsF16Mat4.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiC1C,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Register-resident matvec tile, entirely in fp16: a 4-row x 8-column weight tile held in
|
|
3
|
+
* registers (8 x vec4<f16>, hashed from the thread id so every thread has a
|
|
4
|
+
* different tile), applied to an 8-wide input vector every iteration. Each
|
|
5
|
+
* output row is two 4-wide dot() calls; the 4 outputs then become the next
|
|
6
|
+
* 4 input lanes (the other 4 shift over), so the loop is genuinely
|
|
7
|
+
* loop-carried and can't be hoisted. This is the same dot-product-and-
|
|
8
|
+
* accumulate shape as a real matvec/GEMV inner loop, but with no storage
|
|
9
|
+
* buffer traffic at all: it isolates how fast this dtype's dot products run
|
|
10
|
+
* on the ALU once bandwidth is out of the picture. 32 MACs (64 FLOPs) per
|
|
11
|
+
* loop iteration.
|
|
12
|
+
*/
|
|
13
|
+
export declare const flopsF16MatvecWgsl = "\nenable f16;\n\nstruct Params {\n threads: u32,\n iterations: u32,\n};\n\n@group(0) @binding(0) var<uniform> params: Params;\n@group(0) @binding(1) var<storage, read_write> out: array<f32>;\n\n// Same weight hash as the fp32 kernel, but producing vec4<f16>.\nfn weights(seed: u32) -> vec4<f16> {\n let h = seed * 0x9e3779b1u;\n let bytes = vec4<u32>(h & 0xffu, (h >> 8u) & 0xffu, (h >> 16u) & 0xffu, h >> 24u);\n return (vec4<f16>(bytes) - vec4<f16>(127.5)) * f16(1.0 / 4096.0);\n}\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3<u32>) {\n let idx = gid.x;\n if (idx >= params.threads) {\n return;\n }\n let w00: vec4<f16> = weights(idx * 8u + 1u);\n let w01: vec4<f16> = weights(idx * 8u + 2u);\n let w10: vec4<f16> = weights(idx * 8u + 3u);\n let w11: vec4<f16> = weights(idx * 8u + 4u);\n let w20: vec4<f16> = weights(idx * 8u + 5u);\n let w21: vec4<f16> = weights(idx * 8u + 6u);\n let w30: vec4<f16> = weights(idx * 8u + 7u);\n let w31: vec4<f16> = weights(idx * 8u + 8u);\n var x0: vec4<f16> = vec4<f16>(f16(idx & 255u) * f16(0.001) + f16(0.1), f16(0.2), f16(0.3), f16(0.4));\n var x1: vec4<f16> = vec4<f16>(f16(idx & 255u) * f16(0.001) + f16(0.2), f16(0.4), f16(0.6), f16(0.8));\n for (var i: u32 = 0u; i < params.iterations; i = i + 1u) {\n let o0 = dot(w00, x0) + dot(w01, x1);\n let o1 = dot(w10, x0) + dot(w11, x1);\n let o2 = dot(w20, x0) + dot(w21, x1);\n let o3 = dot(w30, x0) + dot(w31, x1);\n x0 = x1;\n x1 = vec4<f16>(o0, o1, o2, o3);\n }\n out[idx] = f32(x0.x + x0.y + x0.z + x0.w + x1.x + x1.y + x1.z + x1.w);\n}\n";
|
|
14
|
+
//# sourceMappingURL=flopsF16Matvec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flopsF16Matvec.d.ts","sourceRoot":"","sources":["../../src/shaders/flopsF16Matvec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB,+kDA4C9B,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Register-resident matvec tile, entirely in fp16: a 4-row x 8-column weight tile held in
|
|
3
|
+
* registers (8 x vec4<f16>, hashed from the thread id so every thread has a
|
|
4
|
+
* different tile), applied to an 8-wide input vector every iteration. Each
|
|
5
|
+
* output row is two 4-wide dot() calls; the 4 outputs then become the next
|
|
6
|
+
* 4 input lanes (the other 4 shift over), so the loop is genuinely
|
|
7
|
+
* loop-carried and can't be hoisted. This is the same dot-product-and-
|
|
8
|
+
* accumulate shape as a real matvec/GEMV inner loop, but with no storage
|
|
9
|
+
* buffer traffic at all: it isolates how fast this dtype's dot products run
|
|
10
|
+
* on the ALU once bandwidth is out of the picture. 32 MACs (64 FLOPs) per
|
|
11
|
+
* loop iteration.
|
|
12
|
+
*/
|
|
13
|
+
export const flopsF16MatvecWgsl = /* wgsl */ `
|
|
14
|
+
enable f16;
|
|
15
|
+
|
|
16
|
+
struct Params {
|
|
17
|
+
threads: u32,
|
|
18
|
+
iterations: u32,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
@group(0) @binding(0) var<uniform> params: Params;
|
|
22
|
+
@group(0) @binding(1) var<storage, read_write> out: array<f32>;
|
|
23
|
+
|
|
24
|
+
// Same weight hash as the fp32 kernel, but producing vec4<f16>.
|
|
25
|
+
fn weights(seed: u32) -> vec4<f16> {
|
|
26
|
+
let h = seed * 0x9e3779b1u;
|
|
27
|
+
let bytes = vec4<u32>(h & 0xffu, (h >> 8u) & 0xffu, (h >> 16u) & 0xffu, h >> 24u);
|
|
28
|
+
return (vec4<f16>(bytes) - vec4<f16>(127.5)) * f16(1.0 / 4096.0);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@compute @workgroup_size(64)
|
|
32
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
33
|
+
let idx = gid.x;
|
|
34
|
+
if (idx >= params.threads) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
let w00: vec4<f16> = weights(idx * 8u + 1u);
|
|
38
|
+
let w01: vec4<f16> = weights(idx * 8u + 2u);
|
|
39
|
+
let w10: vec4<f16> = weights(idx * 8u + 3u);
|
|
40
|
+
let w11: vec4<f16> = weights(idx * 8u + 4u);
|
|
41
|
+
let w20: vec4<f16> = weights(idx * 8u + 5u);
|
|
42
|
+
let w21: vec4<f16> = weights(idx * 8u + 6u);
|
|
43
|
+
let w30: vec4<f16> = weights(idx * 8u + 7u);
|
|
44
|
+
let w31: vec4<f16> = weights(idx * 8u + 8u);
|
|
45
|
+
var x0: vec4<f16> = vec4<f16>(f16(idx & 255u) * f16(0.001) + f16(0.1), f16(0.2), f16(0.3), f16(0.4));
|
|
46
|
+
var x1: vec4<f16> = vec4<f16>(f16(idx & 255u) * f16(0.001) + f16(0.2), f16(0.4), f16(0.6), f16(0.8));
|
|
47
|
+
for (var i: u32 = 0u; i < params.iterations; i = i + 1u) {
|
|
48
|
+
let o0 = dot(w00, x0) + dot(w01, x1);
|
|
49
|
+
let o1 = dot(w10, x0) + dot(w11, x1);
|
|
50
|
+
let o2 = dot(w20, x0) + dot(w21, x1);
|
|
51
|
+
let o3 = dot(w30, x0) + dot(w31, x1);
|
|
52
|
+
x0 = x1;
|
|
53
|
+
x1 = vec4<f16>(o0, o1, o2, o3);
|
|
54
|
+
}
|
|
55
|
+
out[idx] = f32(x0.x + x0.y + x0.z + x0.w + x1.x + x1.y + x1.z + x1.w);
|
|
56
|
+
}
|
|
57
|
+
`;
|
|
58
|
+
//# sourceMappingURL=flopsF16Matvec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flopsF16Matvec.js","sourceRoot":"","sources":["../../src/shaders/flopsF16Matvec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4C5C,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raw fp16 FLOPS probe — same shape as {@link flopsF32ScalarWgsl} (eight
|
|
3
|
+
* independent chains, unrolled 4x), but every operand and accumulator is
|
|
4
|
+
* `f16`, so the FMA chains run entirely in half precision (only the final
|
|
5
|
+
* store converts back to f32). 64 FLOPs per loop iteration.
|
|
6
|
+
*/
|
|
7
|
+
export declare const flopsF16ScalarWgsl = "\nenable f16;\n\nstruct Params {\n threads: u32,\n iterations: u32,\n};\n\n@group(0) @binding(0) var<uniform> params: Params;\n@group(0) @binding(1) var<storage, read_write> out: array<f32>;\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3<u32>) {\n let idx = gid.x;\n if (idx >= params.threads) {\n return;\n }\n let a: f16 = f16(1.0) - f16(idx & 15u) * f16(0.001);\n let b: f16 = f16(0.5) + f16(idx & 7u) * f16(0.001);\n var x0: f16 = f16(idx & 255u) * f16(0.0001) + f16(0.01);\n var x1: f16 = f16(idx & 255u) * f16(0.0001) + f16(0.02);\n var x2: f16 = f16(idx & 255u) * f16(0.0001) + f16(0.03);\n var x3: f16 = f16(idx & 255u) * f16(0.0001) + f16(0.04);\n var x4: f16 = f16(idx & 255u) * f16(0.0001) + f16(0.05);\n var x5: f16 = f16(idx & 255u) * f16(0.0001) + f16(0.06);\n var x6: f16 = f16(idx & 255u) * f16(0.0001) + f16(0.07);\n var x7: f16 = f16(idx & 255u) * f16(0.0001) + f16(0.08);\n for (var i: u32 = 0u; i < params.iterations; i = i + 1u) {\n x0 = x0 * a + b;\n x1 = x1 * a + b;\n x2 = x2 * a + b;\n x3 = x3 * a + b;\n x4 = x4 * a + b;\n x5 = x5 * a + b;\n x6 = x6 * a + b;\n x7 = x7 * a + b;\n x0 = x0 * a + b;\n x1 = x1 * a + b;\n x2 = x2 * a + b;\n x3 = x3 * a + b;\n x4 = x4 * a + b;\n x5 = x5 * a + b;\n x6 = x6 * a + b;\n x7 = x7 * a + b;\n x0 = x0 * a + b;\n x1 = x1 * a + b;\n x2 = x2 * a + b;\n x3 = x3 * a + b;\n x4 = x4 * a + b;\n x5 = x5 * a + b;\n x6 = x6 * a + b;\n x7 = x7 * a + b;\n x0 = x0 * a + b;\n x1 = x1 * a + b;\n x2 = x2 * a + b;\n x3 = x3 * a + b;\n x4 = x4 * a + b;\n x5 = x5 * a + b;\n x6 = x6 * a + b;\n x7 = x7 * a + b;\n }\n out[idx] = f32(x0 + x1 + x2 + x3 + x4 + x5 + x6 + x7);\n}\n";
|
|
8
|
+
//# sourceMappingURL=flopsF16Scalar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flopsF16Scalar.d.ts","sourceRoot":"","sources":["../../src/shaders/flopsF16Scalar.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,svDA+D9B,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raw fp16 FLOPS probe — same shape as {@link flopsF32ScalarWgsl} (eight
|
|
3
|
+
* independent chains, unrolled 4x), but every operand and accumulator is
|
|
4
|
+
* `f16`, so the FMA chains run entirely in half precision (only the final
|
|
5
|
+
* store converts back to f32). 64 FLOPs per loop iteration.
|
|
6
|
+
*/
|
|
7
|
+
export const flopsF16ScalarWgsl = /* wgsl */ `
|
|
8
|
+
enable f16;
|
|
9
|
+
|
|
10
|
+
struct Params {
|
|
11
|
+
threads: u32,
|
|
12
|
+
iterations: u32,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
@group(0) @binding(0) var<uniform> params: Params;
|
|
16
|
+
@group(0) @binding(1) var<storage, read_write> out: array<f32>;
|
|
17
|
+
|
|
18
|
+
@compute @workgroup_size(64)
|
|
19
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
20
|
+
let idx = gid.x;
|
|
21
|
+
if (idx >= params.threads) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
let a: f16 = f16(1.0) - f16(idx & 15u) * f16(0.001);
|
|
25
|
+
let b: f16 = f16(0.5) + f16(idx & 7u) * f16(0.001);
|
|
26
|
+
var x0: f16 = f16(idx & 255u) * f16(0.0001) + f16(0.01);
|
|
27
|
+
var x1: f16 = f16(idx & 255u) * f16(0.0001) + f16(0.02);
|
|
28
|
+
var x2: f16 = f16(idx & 255u) * f16(0.0001) + f16(0.03);
|
|
29
|
+
var x3: f16 = f16(idx & 255u) * f16(0.0001) + f16(0.04);
|
|
30
|
+
var x4: f16 = f16(idx & 255u) * f16(0.0001) + f16(0.05);
|
|
31
|
+
var x5: f16 = f16(idx & 255u) * f16(0.0001) + f16(0.06);
|
|
32
|
+
var x6: f16 = f16(idx & 255u) * f16(0.0001) + f16(0.07);
|
|
33
|
+
var x7: f16 = f16(idx & 255u) * f16(0.0001) + f16(0.08);
|
|
34
|
+
for (var i: u32 = 0u; i < params.iterations; i = i + 1u) {
|
|
35
|
+
x0 = x0 * a + b;
|
|
36
|
+
x1 = x1 * a + b;
|
|
37
|
+
x2 = x2 * a + b;
|
|
38
|
+
x3 = x3 * a + b;
|
|
39
|
+
x4 = x4 * a + b;
|
|
40
|
+
x5 = x5 * a + b;
|
|
41
|
+
x6 = x6 * a + b;
|
|
42
|
+
x7 = x7 * a + b;
|
|
43
|
+
x0 = x0 * a + b;
|
|
44
|
+
x1 = x1 * a + b;
|
|
45
|
+
x2 = x2 * a + b;
|
|
46
|
+
x3 = x3 * a + b;
|
|
47
|
+
x4 = x4 * a + b;
|
|
48
|
+
x5 = x5 * a + b;
|
|
49
|
+
x6 = x6 * a + b;
|
|
50
|
+
x7 = x7 * a + b;
|
|
51
|
+
x0 = x0 * a + b;
|
|
52
|
+
x1 = x1 * a + b;
|
|
53
|
+
x2 = x2 * a + b;
|
|
54
|
+
x3 = x3 * a + b;
|
|
55
|
+
x4 = x4 * a + b;
|
|
56
|
+
x5 = x5 * a + b;
|
|
57
|
+
x6 = x6 * a + b;
|
|
58
|
+
x7 = x7 * a + b;
|
|
59
|
+
x0 = x0 * a + b;
|
|
60
|
+
x1 = x1 * a + b;
|
|
61
|
+
x2 = x2 * a + b;
|
|
62
|
+
x3 = x3 * a + b;
|
|
63
|
+
x4 = x4 * a + b;
|
|
64
|
+
x5 = x5 * a + b;
|
|
65
|
+
x6 = x6 * a + b;
|
|
66
|
+
x7 = x7 * a + b;
|
|
67
|
+
}
|
|
68
|
+
out[idx] = f32(x0 + x1 + x2 + x3 + x4 + x5 + x6 + x7);
|
|
69
|
+
}
|
|
70
|
+
`;
|
|
71
|
+
//# sourceMappingURL=flopsF16Scalar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flopsF16Scalar.js","sourceRoot":"","sources":["../../src/shaders/flopsF16Scalar.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+D5C,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fp16 vec4 FLOPS probe — a single FMA chain held in a `vec4<f16>` register
|
|
3
|
+
* (4 independent half-precision lanes per step; only GPUs with packed-half
|
|
4
|
+
* ALUs run this faster than the fp32 vec4 probe).
|
|
5
|
+
*/
|
|
6
|
+
export declare const flopsF16Vec4Wgsl = "\nenable f16;\n\nstruct Params {\n threads: u32,\n iterations: u32,\n};\n\n@group(0) @binding(0) var<uniform> params: Params;\n@group(0) @binding(1) var<storage, read_write> out: array<f32>;\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3<u32>) {\n let idx = gid.x;\n if (idx >= params.threads) {\n return;\n }\n let s: f16 = f16(idx & 15u) * f16(0.001);\n let half: f16 = f16(0.5);\n let one: f16 = f16(1.0);\n let a: vec4<f16> = vec4<f16>(one - s, one - f16(2.0) * s, one - f16(3.0) * s, one - f16(4.0) * s);\n let b: vec4<f16> = vec4<f16>(half + s, half + f16(2.0) * s, half + f16(3.0) * s, half + f16(4.0) * s);\n var x: vec4<f16> = vec4<f16>(f16(idx & 255u) * f16(0.0001));\n for (var i: u32 = 0u; i < params.iterations; i = i + 1u) {\n x = x * a + b;\n }\n out[idx] = f32(x.x + x.y + x.z + x.w);\n}\n";
|
|
7
|
+
//# sourceMappingURL=flopsF16Vec4.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flopsF16Vec4.d.ts","sourceRoot":"","sources":["../../src/shaders/flopsF16Vec4.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,i2BA4B5B,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fp16 vec4 FLOPS probe — a single FMA chain held in a `vec4<f16>` register
|
|
3
|
+
* (4 independent half-precision lanes per step; only GPUs with packed-half
|
|
4
|
+
* ALUs run this faster than the fp32 vec4 probe).
|
|
5
|
+
*/
|
|
6
|
+
export const flopsF16Vec4Wgsl = /* wgsl */ `
|
|
7
|
+
enable f16;
|
|
8
|
+
|
|
9
|
+
struct Params {
|
|
10
|
+
threads: u32,
|
|
11
|
+
iterations: u32,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
@group(0) @binding(0) var<uniform> params: Params;
|
|
15
|
+
@group(0) @binding(1) var<storage, read_write> out: array<f32>;
|
|
16
|
+
|
|
17
|
+
@compute @workgroup_size(64)
|
|
18
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
19
|
+
let idx = gid.x;
|
|
20
|
+
if (idx >= params.threads) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
let s: f16 = f16(idx & 15u) * f16(0.001);
|
|
24
|
+
let half: f16 = f16(0.5);
|
|
25
|
+
let one: f16 = f16(1.0);
|
|
26
|
+
let a: vec4<f16> = vec4<f16>(one - s, one - f16(2.0) * s, one - f16(3.0) * s, one - f16(4.0) * s);
|
|
27
|
+
let b: vec4<f16> = vec4<f16>(half + s, half + f16(2.0) * s, half + f16(3.0) * s, half + f16(4.0) * s);
|
|
28
|
+
var x: vec4<f16> = vec4<f16>(f16(idx & 255u) * f16(0.0001));
|
|
29
|
+
for (var i: u32 = 0u; i < params.iterations; i = i + 1u) {
|
|
30
|
+
x = x * a + b;
|
|
31
|
+
}
|
|
32
|
+
out[idx] = f32(x.x + x.y + x.z + x.w);
|
|
33
|
+
}
|
|
34
|
+
`;
|
|
35
|
+
//# sourceMappingURL=flopsF16Vec4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flopsF16Vec4.js","sourceRoot":"","sources":["../../src/shaders/flopsF16Vec4.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4B1C,CAAC"}
|